From 250fe67828bb1af012b5f8f6a00ef0c4e5ca984a Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Thu, 5 Nov 2020 12:23:57 -0600 Subject: [PATCH 01/81] [Metrics UI] Add full custom metric UI to inventory alerts (#81929) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../inventory/components/expression.test.tsx | 4 +- .../inventory/components/expression.tsx | 85 ++---- .../alerting/inventory/components/metric.tsx | 273 +++++++++++++++--- .../inventory/components/validation.tsx | 14 +- 4 files changed, 282 insertions(+), 94 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx index 60a00371e5ade5..54d3b783d22f6c 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx @@ -12,7 +12,7 @@ import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/ap // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types'; import React from 'react'; -import { Expressions, AlertContextMeta, ExpressionRow } from './expression'; +import { Expressions, AlertContextMeta, ExpressionRow, defaultExpression } from './expression'; import { act } from 'react-dom/test-utils'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { Comparator } from '../../../../server/lib/alerting/metric_threshold/types'; @@ -105,6 +105,7 @@ describe('Expression', () => { threshold: [], timeSize: 1, timeUnit: 'm', + customMetric: defaultExpression.customMetric, }, ]); }); @@ -155,6 +156,7 @@ describe('ExpressionRow', () => { alertsContextMetadata={{ customMetrics: [], }} + fields={[{ name: 'some.system.field', type: 'bzzz' }]} /> ); diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 66d547eb50d9c2..097e0f1f1690b4 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { set } from '@elastic/safer-lodash-set'; -import { debounce, pick, uniqBy, isEqual } from 'lodash'; +import { debounce, pick } from 'lodash'; import { Unit } from '@elastic/datemath'; import React, { useCallback, useMemo, useEffect, useState, ChangeEvent } from 'react'; +import { IFieldType } from 'src/plugins/data/public'; import { EuiFlexGroup, EuiFlexItem, @@ -23,7 +23,6 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertPreview } from '../../common'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; @@ -95,13 +94,18 @@ interface Props { setAlertProperty(key: string, value: any): void; } -const defaultExpression = { +export const defaultExpression = { metric: 'cpu' as SnapshotMetricType, comparator: Comparator.GT, threshold: [], timeSize: 1, timeUnit: 'm', - customMetric: undefined, + customMetric: { + type: 'custom', + id: 'alert-custom-metric', + field: '', + aggregation: 'avg', + }, } as InventoryMetricConditions; export const Expressions: React.FC = (props) => { @@ -226,7 +230,7 @@ export const Expressions: React.FC = (props) => { metric: md.options.metric!.type, customMetric: SnapshotCustomMetricInputRT.is(md.options.metric) ? md.options.metric - : undefined, + : defaultExpression.customMetric, } as InventoryMetricConditions, ]); } else { @@ -306,6 +310,7 @@ export const Expressions: React.FC = (props) => { errors={errors[idx] || emptyError} expression={e || {}} alertsContextMetadata={alertsContext.metadata} + fields={derivedIndexPattern.fields} /> ); })} @@ -415,6 +420,7 @@ interface ExpressionRowProps { remove(id: number): void; setAlertParams(id: number, params: Partial): void; alertsContextMetadata: AlertsContextValue['metadata']; + fields: IFieldType[]; } const StyledExpressionRow = euiStyled(EuiFlexGroup)` @@ -428,48 +434,25 @@ const StyledExpression = euiStyled.div` `; export const ExpressionRow: React.FC = (props) => { - const { - setAlertParams, - expression, - errors, - expressionId, - remove, - canDelete, - alertsContextMetadata, - } = props; + const { setAlertParams, expression, errors, expressionId, remove, canDelete, fields } = props; const { metric, comparator = Comparator.GT, threshold = [], customMetric } = expression; - const [customMetrics, updateCustomMetrics] = useState([]); - - // Create and uniquify a list of custom metrics including: - // - The alert metadata context (which only gives us custom metrics on the inventory page) - // - The custom metric stored in the expression (necessary when editing this alert without having - // access to the metadata context) - // - Whatever custom metrics were previously stored in this list (to preserve the custom metric in the dropdown - // if the user edits the alert and switches away from the custom metric) - useEffect(() => { - const ctxCustomMetrics = alertsContextMetadata?.customMetrics ?? []; - const expressionCustomMetrics = customMetric ? [customMetric] : []; - const newCustomMetrics = uniqBy( - [...customMetrics, ...ctxCustomMetrics, ...expressionCustomMetrics], - (cm: SnapshotCustomMetricInput) => cm.id - ); - if (!isEqual(customMetrics, newCustomMetrics)) updateCustomMetrics(newCustomMetrics); - }, [alertsContextMetadata, customMetric, customMetrics, updateCustomMetrics]); const updateMetric = useCallback( (m?: SnapshotMetricType | string) => { - const newMetric = SnapshotMetricTypeRT.is(m) ? m : 'custom'; + const newMetric = SnapshotMetricTypeRT.is(m) ? m : Boolean(m) ? 'custom' : undefined; const newAlertParams = { ...expression, metric: newMetric }; - if (newMetric === 'custom' && customMetrics) { - set( - newAlertParams, - 'customMetric', - customMetrics.find((cm) => cm.id === m) - ); - } setAlertParams(expressionId, newAlertParams); }, - [expressionId, expression, setAlertParams, customMetrics] + [expressionId, expression, setAlertParams] + ); + + const updateCustomMetric = useCallback( + (cm?: SnapshotCustomMetricInput) => { + if (SnapshotCustomMetricInputRT.is(cm)) { + setAlertParams(expressionId, { ...expression, customMetric: cm }); + } + }, + [expressionId, expression, setAlertParams] ); const updateComparator = useCallback( @@ -515,17 +498,8 @@ export const ExpressionRow: React.FC = (props) => { myMetrics = containerMetricTypes; break; } - const baseMetricOpts = myMetrics.map(toMetricOpt); - const customMetricOpts = customMetrics - ? customMetrics.map((m, i) => ({ - text: getCustomMetricLabel(m), - value: m.id, - })) - : []; - return [...baseMetricOpts, ...customMetricOpts]; - }, [props.nodeType, customMetrics]); - - const selectedMetricValue = metric === 'custom' && customMetric ? customMetric.id : metric!; + return myMetrics.map(toMetricOpt); + }, [props.nodeType]); return ( <> @@ -535,8 +509,8 @@ export const ExpressionRow: React.FC = (props) => { v?.value === selectedMetricValue)?.text || '', + value: metric!, + text: ofFields.find((v) => v?.value === metric)?.text || '', }} metrics={ ofFields.filter((m) => m !== undefined && m.value !== undefined) as Array<{ @@ -545,7 +519,10 @@ export const ExpressionRow: React.FC = (props) => { }> } onChange={updateMetric} + onChangeCustom={updateCustomMetric} errors={errors} + customMetric={customMetric} + fields={fields} /> diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx index 5418eab3c5fc22..2dd2938dfd55af 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { debounce } from 'lodash'; import { EuiExpression, EuiPopover, @@ -14,16 +15,33 @@ import { EuiFlexItem, EuiFormRow, EuiComboBox, + EuiButtonGroup, + EuiSpacer, + EuiSelect, + EuiText, + EuiFieldText, } from '@elastic/eui'; +import { IFieldType } from 'src/plugins/data/public'; import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui'; +import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { IErrorObject } from '../../../../../triggers_actions_ui/public/types'; +import { + SnapshotCustomMetricInput, + SnapshotCustomMetricInputRT, + SnapshotCustomAggregation, + SNAPSHOT_CUSTOM_AGGREGATIONS, + SnapshotCustomAggregationRT, +} from '../../../../common/http_api/snapshot_api'; interface Props { metric?: { value: string; text: string }; metrics: Array<{ value: string; text: string }>; errors: IErrorObject; onChange: (metric?: string) => void; + onChangeCustom: (customMetric?: SnapshotCustomMetricInput) => void; + customMetric?: SnapshotCustomMetricInput; + fields: IFieldType[]; popupPosition?: | 'upCenter' | 'upLeft' @@ -39,8 +57,40 @@ interface Props { | 'rightDown'; } -export const MetricExpression = ({ metric, metrics, errors, onChange, popupPosition }: Props) => { - const [aggFieldPopoverOpen, setAggFieldPopoverOpen] = useState(false); +const AGGREGATION_LABELS = { + ['avg']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.avg', { + defaultMessage: 'Average', + }), + ['max']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.max', { + defaultMessage: 'Max', + }), + ['min']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.min', { + defaultMessage: 'Min', + }), + ['rate']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.rate', { + defaultMessage: 'Rate', + }), +}; +const aggregationOptions = SNAPSHOT_CUSTOM_AGGREGATIONS.map((k) => ({ + text: AGGREGATION_LABELS[k as SnapshotCustomAggregation], + value: k, +})); + +export const MetricExpression = ({ + metric, + metrics, + customMetric, + fields, + errors, + onChange, + onChangeCustom, + popupPosition, +}: Props) => { + const [popoverOpen, setPopoverOpen] = useState(false); + const [customMetricTabOpen, setCustomMetricTabOpen] = useState(metric?.value === 'custom'); + const [selectedOption, setSelectedOption] = useState(metric?.value); + const [fieldDisplayedCustomLabel, setFieldDisplayedCustomLabel] = useState(customMetric?.label); + const firstFieldOption = { text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel', { defaultMessage: 'Select a metric', @@ -48,13 +98,84 @@ export const MetricExpression = ({ metric, metrics, errors, onChange, popupPosit value: '', }; + const fieldOptions = useMemo( + () => + fields + .filter((f) => f.aggregatable && f.type === 'number' && !(customMetric?.field === f.name)) + .map((f) => ({ label: f.name })), + [fields, customMetric?.field] + ); + + const expressionDisplayValue = useMemo( + () => { + return customMetricTabOpen + ? customMetric?.field && getCustomMetricLabel(customMetric) + : metric?.text || firstFieldOption.text; + }, + // The ?s are confusing eslint here, so... + // eslint-disable-next-line react-hooks/exhaustive-deps + [customMetricTabOpen, metric, customMetric, firstFieldOption] + ); + + const onChangeTab = useCallback( + (id) => { + if (id === 'metric-popover-custom') { + setCustomMetricTabOpen(true); + onChange('custom'); + } else { + setCustomMetricTabOpen(false); + onChange(selectedOption); + } + }, + [setCustomMetricTabOpen, onChange, selectedOption] + ); + + const onAggregationChange = useCallback( + (e) => { + const value = e.target.value; + const aggValue: SnapshotCustomAggregation = SnapshotCustomAggregationRT.is(value) + ? value + : 'avg'; + const newCustomMetric = { + ...customMetric, + aggregation: aggValue, + }; + if (SnapshotCustomMetricInputRT.is(newCustomMetric)) onChangeCustom(newCustomMetric); + }, + [customMetric, onChangeCustom] + ); + + const onFieldChange = useCallback( + (selectedOptions: Array<{ label: string }>) => { + const newCustomMetric = { + ...customMetric, + field: selectedOptions[0].label, + }; + if (SnapshotCustomMetricInputRT.is(newCustomMetric)) onChangeCustom(newCustomMetric); + }, + [customMetric, onChangeCustom] + ); + + const debouncedOnChangeCustom = debounce(onChangeCustom, 500); + const onLabelChange = useCallback( + (e) => { + setFieldDisplayedCustomLabel(e.target.value); + const newCustomMetric = { + ...customMetric, + label: e.target.value, + }; + if (SnapshotCustomMetricInputRT.is(newCustomMetric)) debouncedOnChangeCustom(newCustomMetric); + }, + [customMetric, debouncedOnChangeCustom] + ); + const availablefieldsOptions = metrics.map((m) => { return { label: m.text, value: m.value }; }, []); return ( 0))} + value={expressionDisplayValue} + isActive={Boolean(popoverOpen || (errors.metric && errors.metric.length > 0))} onClick={() => { - setAggFieldPopoverOpen(true); + setPopoverOpen(true); }} color={errors.metric?.length ? 'danger' : 'secondary'} /> } - isOpen={aggFieldPopoverOpen} + isOpen={popoverOpen} closePopover={() => { - setAggFieldPopoverOpen(false); + setPopoverOpen(false); }} anchorPosition={popupPosition ?? 'downRight'} zIndex={8000} > -
- setAggFieldPopoverOpen(false)}> +
+ setPopoverOpen(false)}> - - - 0} error={errors.metric}> - + + {customMetricTabOpen ? ( + <> + + + + + + + + + {i18n.translate('xpack.infra.waffle.customMetrics.of', { + defaultMessage: 'of', + })} + + + + + 0} + /> + + + + of ".', + })} + > + 0} - placeholder={firstFieldOption.text} - options={availablefieldsOptions} - noSuggestions={!availablefieldsOptions.length} - selectedOptions={ - metric ? availablefieldsOptions.filter((a) => a.value === metric.value) : [] - } - renderOption={(o: any) => o.label} - onChange={(selectedOptions) => { - if (selectedOptions.length > 0) { - onChange(selectedOptions[0].value); - setAggFieldPopoverOpen(false); - } else { - onChange(); - } - }} + onChange={onLabelChange} /> - - + + ) : ( + + + + 0} + placeholder={firstFieldOption.text} + options={availablefieldsOptions} + noSuggestions={!availablefieldsOptions.length} + selectedOptions={ + metric ? availablefieldsOptions.filter((a) => a.value === metric.value) : [] + } + renderOption={(o: any) => o.label} + onChange={(selectedOptions) => { + if (selectedOptions.length > 0) { + onChange(selectedOptions[0].value); + setSelectedOption(selectedOptions[0].value); + } else { + onChange(); + } + }} + /> + + + + )}
); diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx index 47ecd3c527fadd..4b522d7d97730b 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx @@ -6,14 +6,14 @@ import { i18n } from '@kbn/i18n'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { MetricExpressionParams } from '../../../../server/lib/alerting/metric_threshold/types'; +import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; export function validateMetricThreshold({ criteria, }: { - criteria: MetricExpressionParams[]; + criteria: InventoryMetricConditions[]; }): ValidationResult { const validationResult = { errors: {} }; const errors: { @@ -81,14 +81,20 @@ export function validateMetricThreshold({ }) ); } - - if (!c.metric && c.aggType !== 'count') { + if (!c.metric) { errors[id].metric.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', { defaultMessage: 'Metric is required.', }) ); } + if (c.metric === 'custom' && !c.customMetric?.field) { + errors[id].metric.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.customMetricFieldRequired', { + defaultMessage: 'Field is required.', + }) + ); + } }); return validationResult; From 9bff56df7d28b7471830be798262d30579e537a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Thu, 5 Nov 2020 19:37:21 +0100 Subject: [PATCH 02/81] [Security Solution] Fix Overview cypress tests (#82761) --- .../security_solution/cypress/integration/overview.spec.ts | 4 ++-- .../plugins/security_solution/cypress/support/commands.js | 3 +-- x-pack/plugins/security_solution/cypress/support/index.d.ts | 6 +----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index 9e46a537030412..69094cad7456e9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -13,7 +13,7 @@ import { OVERVIEW_URL } from '../urls/navigation'; describe('Overview Page', () => { it('Host stats render with correct values', () => { - cy.stubSearchStrategyApi('overviewHostQuery', 'overview_search_strategy'); + cy.stubSearchStrategyApi('overview_search_strategy'); loginAndWaitForPage(OVERVIEW_URL); expandHostStats(); @@ -23,7 +23,7 @@ describe('Overview Page', () => { }); it('Network stats render with correct values', () => { - cy.stubSearchStrategyApi('overviewNetworkQuery', 'overview_search_strategy'); + cy.stubSearchStrategyApi('overview_search_strategy'); loginAndWaitForPage(OVERVIEW_URL); expandNetworkStats(); diff --git a/x-pack/plugins/security_solution/cypress/support/commands.js b/x-pack/plugins/security_solution/cypress/support/commands.js index dbd60cdd31a5a6..e13a76736205c7 100644 --- a/x-pack/plugins/security_solution/cypress/support/commands.js +++ b/x-pack/plugins/security_solution/cypress/support/commands.js @@ -40,7 +40,6 @@ Cypress.Commands.add('stubSecurityApi', function (dataFileName) { }); Cypress.Commands.add('stubSearchStrategyApi', function ( - queryId, dataFileName, searchStrategyName = 'securitySolutionSearchStrategy' ) { @@ -49,7 +48,7 @@ Cypress.Commands.add('stubSearchStrategyApi', function ( }); cy.server(); cy.fixture(dataFileName).as(`${dataFileName}JSON`); - cy.route('POST', `internal/search/${searchStrategyName}/${queryId}`, `@${dataFileName}JSON`); + cy.route('POST', `internal/search/${searchStrategyName}`, `@${dataFileName}JSON`); }); Cypress.Commands.add( diff --git a/x-pack/plugins/security_solution/cypress/support/index.d.ts b/x-pack/plugins/security_solution/cypress/support/index.d.ts index 0cf3cf614cdb9e..fb55a2890c8b7f 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.d.ts +++ b/x-pack/plugins/security_solution/cypress/support/index.d.ts @@ -8,11 +8,7 @@ declare namespace Cypress { interface Chainable { promisify(): Promise; stubSecurityApi(dataFileName: string): Chainable; - stubSearchStrategyApi( - queryId: string, - dataFileName: string, - searchStrategyName?: string - ): Chainable; + stubSearchStrategyApi(dataFileName: string, searchStrategyName?: string): Chainable; attachFile(fileName: string, fileType?: string): Chainable; waitUntil( fn: (subject: Subject) => boolean | Chainable, From 62443a6a0574343f0ee7fd448328369e748aee32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 5 Nov 2020 16:29:27 -0300 Subject: [PATCH 03/81] [APM] Filtering by "Type" on error overview sometimes causes an error --- .../__test__/__snapshots__/List.test.tsx.snap | 24 +++++++++---------- .../app/ErrorGroupOverview/List/index.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index b5a558621e9ca9..1f34a0cef1ccf5 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -826,7 +826,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` query={ Object { "end": "2018-01-10T10:06:41.050Z", - "kuery": "error.exception.type:AssertionError", + "kuery": "error.exception.type:\\"AssertionError\\"", "page": 0, "serviceName": "opbeans-python", "start": "2018-01-10T09:51:41.050Z", @@ -838,12 +838,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1065,7 +1065,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` query={ Object { "end": "2018-01-10T10:06:41.050Z", - "kuery": "error.exception.type:AssertionError", + "kuery": "error.exception.type:\\"AssertionError\\"", "page": 0, "serviceName": "opbeans-python", "start": "2018-01-10T09:51:41.050Z", @@ -1077,12 +1077,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1304,7 +1304,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` query={ Object { "end": "2018-01-10T10:06:41.050Z", - "kuery": "error.exception.type:AssertionError", + "kuery": "error.exception.type:\\"AssertionError\\"", "page": 0, "serviceName": "opbeans-python", "start": "2018-01-10T09:51:41.050Z", @@ -1316,12 +1316,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1543,7 +1543,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` query={ Object { "end": "2018-01-10T10:06:41.050Z", - "kuery": "error.exception.type:AssertionError", + "kuery": "error.exception.type:\\"AssertionError\\"", "page": 0, "serviceName": "opbeans-python", "start": "2018-01-10T09:51:41.050Z", @@ -1555,12 +1555,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index 33105189f9c3e4..e1f6239112555e 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -107,7 +107,7 @@ function ErrorGroupList({ items, serviceName }: Props) { query={ { ...urlParams, - kuery: `error.exception.type:${type}`, + kuery: `error.exception.type:"${type}"`, } as APMQueryParams } > From eeebe580e31f08b04953bf13d95c7f35945a37f5 Mon Sep 17 00:00:00 2001 From: Stuart Tettemer Date: Thu, 5 Nov 2020 13:30:15 -0600 Subject: [PATCH 04/81] Docs: Remove references to Goovy, JS and Py scripted fields (#82662) --- docs/management/managing-fields.asciidoc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index 3734655edd91b3..e69b147615669b 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -121,12 +121,8 @@ WARNING: Computing data on the fly with scripted fields can be very resource int {kib} performance. Keep in mind that there's no built-in validation of a scripted field. If your scripts are buggy, you'll get exceptions whenever you try to view the dynamically generated data. -When you define a scripted field in {kib}, you have a choice of scripting languages. In 5.0 and later, the default -options are {ref}/modules-scripting-expression.html[Lucene expressions] and {ref}/modules-scripting-painless.html[Painless]. -While you can use other scripting languages if you enable dynamic scripting for them in {es}, this is not recommended -because they cannot be sufficiently {ref}/modules-scripting-security.html[sandboxed]. - -WARNING: In 5.0 and later, Groovy, JavaScript, and Python scripting are deprecated and unsupported. +When you define a scripted field in {kib}, you have a choice of the {ref}/modules-scripting-expression.html[Lucene expressions] or the +{ref}/modules-scripting-painless.html[Painless] scripting language. You can reference any single value numeric field in your expressions, for example: From c584376ef75e7e138719d1a108bb05ae09753310 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 5 Nov 2020 14:58:31 -0500 Subject: [PATCH 05/81] Skip failing suite (#81848) --- .../security_solution/cypress/integration/overview.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index 69094cad7456e9..dafcabb8e1e8df 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -11,7 +11,8 @@ import { loginAndWaitForPage } from '../tasks/login'; import { OVERVIEW_URL } from '../urls/navigation'; -describe('Overview Page', () => { +// Failing: See https://github.com/elastic/kibana/issues/81848 +describe.skip('Overview Page', () => { it('Host stats render with correct values', () => { cy.stubSearchStrategyApi('overview_search_strategy'); loginAndWaitForPage(OVERVIEW_URL); From 64371392b0d795f63736f5cfbb4557a369a026af Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Thu, 5 Nov 2020 15:06:31 -0500 Subject: [PATCH 06/81] [Fleet] Remove asterix from test file name (#82721) * Revert "Revert "[Fleet] Allow snake cased Kibana assets (#77515)" (#82706)" This reverts commit bc05e79b850615ae42cd9eb9e542a8d85c845799. * Rename test index pattern --- .../package_to_package_policy.test.ts | 2 +- .../ingest_manager/common/types/models/epm.ts | 16 ++- .../ingest_manager/sections/epm/constants.tsx | 4 +- .../server/routes/data_streams/handlers.ts | 4 +- .../services/epm/kibana/assets/install.ts | 114 +++++++++++++++--- .../epm/kibana/index_pattern/install.ts | 2 +- .../ensure_installed_default_packages.test.ts | 4 +- .../epm/packages/get_install_type.test.ts | 6 +- .../server/services/epm/packages/install.ts | 5 +- .../server/services/epm/packages/remove.ts | 42 +++++-- .../server/services/epm/registry/index.ts | 4 +- .../ingest_manager/server/types/index.tsx | 1 + .../apis/epm/install_remove_assets.ts | 33 +++++ .../apis/epm/update_assets.ts | 8 +- .../0.1.0/kibana/index_pattern/invalid.json | 11 ++ .../index_pattern/test_index_pattern.json | 11 ++ 16 files changed, 219 insertions(+), 48 deletions(-) create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/invalid.json create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/test_index_pattern.json diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts index 8927b5ab3ca4b8..91396bce359b04 100644 --- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts @@ -25,7 +25,7 @@ describe('Ingest Manager - packageToPackagePolicy', () => { dashboard: [], visualization: [], search: [], - 'index-pattern': [], + index_pattern: [], map: [], }, }, diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index a32322ecff62a9..c5fc208bfb2dc8 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -35,7 +35,21 @@ export type ServiceName = 'kibana' | 'elasticsearch'; export type AgentAssetType = typeof agentAssetTypes; export type AssetType = KibanaAssetType | ElasticsearchAssetType | ValueOf; +/* + Enum mapping of a saved object asset type to how it would appear in a package file path (snake cased) +*/ export enum KibanaAssetType { + dashboard = 'dashboard', + visualization = 'visualization', + search = 'search', + indexPattern = 'index_pattern', + map = 'map', +} + +/* + Enum of saved object types that are allowed to be installed +*/ +export enum KibanaSavedObjectType { dashboard = 'dashboard', visualization = 'visualization', search = 'search', @@ -271,7 +285,7 @@ export type NotInstalled = T & { export type AssetReference = KibanaAssetReference | EsAssetReference; export type KibanaAssetReference = Pick & { - type: KibanaAssetType; + type: KibanaSavedObjectType; }; export type EsAssetReference = Pick & { type: ElasticsearchAssetType; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx index da3cab1a4b8a3d..1dad25e9cf0595 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx @@ -20,7 +20,7 @@ export const AssetTitleMap: Record = { ilm_policy: 'ILM Policy', ingest_pipeline: 'Ingest Pipeline', transform: 'Transform', - 'index-pattern': 'Index Pattern', + index_pattern: 'Index Pattern', index_template: 'Index Template', component_template: 'Component Template', search: 'Saved Search', @@ -36,7 +36,7 @@ export const ServiceTitleMap: Record = { export const AssetIcons: Record = { dashboard: 'dashboardApp', - 'index-pattern': 'indexPatternApp', + index_pattern: 'indexPatternApp', search: 'searchProfilerApp', visualization: 'visualizeApp', map: 'mapApp', diff --git a/x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts index 652a7789f65a30..f42f5da2695d06 100644 --- a/x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts @@ -5,7 +5,7 @@ */ import { RequestHandler, SavedObjectsClientContract } from 'src/core/server'; import { DataStream } from '../../types'; -import { GetDataStreamsResponse, KibanaAssetType } from '../../../common'; +import { GetDataStreamsResponse, KibanaAssetType, KibanaSavedObjectType } from '../../../common'; import { getPackageSavedObjects, getKibanaSavedObject } from '../../services/epm/packages/get'; import { defaultIngestErrorHandler } from '../../errors'; @@ -124,7 +124,7 @@ export const getListHandler: RequestHandler = async (context, request, response) // then pick the dashboards from the package saved object const dashboards = pkgSavedObject[0].attributes?.installed_kibana?.filter( - (o) => o.type === KibanaAssetType.dashboard + (o) => o.type === KibanaSavedObjectType.dashboard ) || []; // and then pick the human-readable titles from the dashboard saved objects const enhancedDashboards = await getEnhancedDashboards( diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts index 201003629e5ea3..e7b251ef133c5b 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts @@ -11,17 +11,49 @@ import { } from 'src/core/server'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../../common'; import * as Registry from '../../registry'; -import { AssetType, KibanaAssetType, AssetReference } from '../../../../types'; +import { + AssetType, + KibanaAssetType, + AssetReference, + AssetParts, + KibanaSavedObjectType, +} from '../../../../types'; import { savedObjectTypes } from '../../packages'; +import { indexPatternTypes } from '../index_pattern/install'; type SavedObjectToBe = Required> & { - type: AssetType; + type: KibanaSavedObjectType; }; export type ArchiveAsset = Pick< SavedObject, 'id' | 'attributes' | 'migrationVersion' | 'references' > & { - type: AssetType; + type: KibanaSavedObjectType; +}; + +// KibanaSavedObjectTypes are used to ensure saved objects being created for a given +// KibanaAssetType have the correct type +const KibanaSavedObjectTypeMapping: Record = { + [KibanaAssetType.dashboard]: KibanaSavedObjectType.dashboard, + [KibanaAssetType.indexPattern]: KibanaSavedObjectType.indexPattern, + [KibanaAssetType.map]: KibanaSavedObjectType.map, + [KibanaAssetType.search]: KibanaSavedObjectType.search, + [KibanaAssetType.visualization]: KibanaSavedObjectType.visualization, +}; + +// Define how each asset type will be installed +const AssetInstallers: Record< + KibanaAssetType, + (args: { + savedObjectsClient: SavedObjectsClientContract; + kibanaAssets: ArchiveAsset[]; + }) => Promise>> +> = { + [KibanaAssetType.dashboard]: installKibanaSavedObjects, + [KibanaAssetType.indexPattern]: installKibanaIndexPatterns, + [KibanaAssetType.map]: installKibanaSavedObjects, + [KibanaAssetType.search]: installKibanaSavedObjects, + [KibanaAssetType.visualization]: installKibanaSavedObjects, }; export async function getKibanaAsset(key: string): Promise { @@ -47,16 +79,22 @@ export function createSavedObjectKibanaAsset(asset: ArchiveAsset): SavedObjectTo export async function installKibanaAssets(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; - kibanaAssets: ArchiveAsset[]; + kibanaAssets: Record; }): Promise { const { savedObjectsClient, kibanaAssets } = options; // install the assets const kibanaAssetTypes = Object.values(KibanaAssetType); const installedAssets = await Promise.all( - kibanaAssetTypes.map((assetType) => - installKibanaSavedObjects({ savedObjectsClient, assetType, kibanaAssets }) - ) + kibanaAssetTypes.map((assetType) => { + if (kibanaAssets[assetType]) { + return AssetInstallers[assetType]({ + savedObjectsClient, + kibanaAssets: kibanaAssets[assetType], + }); + } + return []; + }) ); return installedAssets.flat(); } @@ -74,25 +112,50 @@ export const deleteKibanaInstalledRefs = async ( installed_kibana: installedAssetsToSave, }); }; -export async function getKibanaAssets(paths: string[]) { - const isKibanaAssetType = (path: string) => Registry.pathParts(path).type in KibanaAssetType; - const filteredPaths = paths.filter(isKibanaAssetType); - const kibanaAssets = await Promise.all(filteredPaths.map((path) => getKibanaAsset(path))); - return kibanaAssets; +export async function getKibanaAssets( + paths: string[] +): Promise> { + const kibanaAssetTypes = Object.values(KibanaAssetType); + const isKibanaAssetType = (path: string) => { + const parts = Registry.pathParts(path); + + return parts.service === 'kibana' && (kibanaAssetTypes as string[]).includes(parts.type); + }; + + const filteredPaths = paths + .filter(isKibanaAssetType) + .map<[string, AssetParts]>((path) => [path, Registry.pathParts(path)]); + + const assetArrays: Array> = []; + for (const assetType of kibanaAssetTypes) { + const matching = filteredPaths.filter(([path, parts]) => parts.type === assetType); + + assetArrays.push(Promise.all(matching.map(([path]) => path).map(getKibanaAsset))); + } + + const resolvedAssets = await Promise.all(assetArrays); + + const result = {} as Record; + + for (const [index, assetType] of kibanaAssetTypes.entries()) { + const expectedType = KibanaSavedObjectTypeMapping[assetType]; + const properlyTypedAssets = resolvedAssets[index].filter(({ type }) => type === expectedType); + + result[assetType] = properlyTypedAssets; + } + + return result; } + async function installKibanaSavedObjects({ savedObjectsClient, - assetType, kibanaAssets, }: { savedObjectsClient: SavedObjectsClientContract; - assetType: KibanaAssetType; kibanaAssets: ArchiveAsset[]; }) { - const isSameType = (asset: ArchiveAsset) => assetType === asset.type; - const filteredKibanaAssets = kibanaAssets.filter((asset) => isSameType(asset)); const toBeSavedObjects = await Promise.all( - filteredKibanaAssets.map((asset) => createSavedObjectKibanaAsset(asset)) + kibanaAssets.map((asset) => createSavedObjectKibanaAsset(asset)) ); if (toBeSavedObjects.length === 0) { @@ -105,8 +168,23 @@ async function installKibanaSavedObjects({ } } +async function installKibanaIndexPatterns({ + savedObjectsClient, + kibanaAssets, +}: { + savedObjectsClient: SavedObjectsClientContract; + kibanaAssets: ArchiveAsset[]; +}) { + // Filter out any reserved index patterns + const reservedPatterns = indexPatternTypes.map((pattern) => `${pattern}-*`); + + const nonReservedPatterns = kibanaAssets.filter((asset) => !reservedPatterns.includes(asset.id)); + + return installKibanaSavedObjects({ savedObjectsClient, kibanaAssets: nonReservedPatterns }); +} + export function toAssetReference({ id, type }: SavedObject) { - const reference: AssetReference = { id, type: type as KibanaAssetType }; + const reference: AssetReference = { id, type: type as KibanaSavedObjectType }; return reference; } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts index 4ca8e9d52c337e..d18f43d62436a4 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts @@ -72,6 +72,7 @@ export interface IndexPatternField { readFromDocValues: boolean; } +export const indexPatternTypes = Object.values(dataTypes); // TODO: use a function overload and make pkgName and pkgVersion required for install/update // and not for an update removal. or separate out the functions export async function installIndexPatterns( @@ -116,7 +117,6 @@ export async function installIndexPatterns( const packageVersionsInfo = await Promise.all(packageVersionsFetchInfoPromise); // for each index pattern type, create an index pattern - const indexPatternTypes = Object.values(dataTypes); indexPatternTypes.forEach(async (indexPatternType) => { // if this is an update because a package is being uninstalled (no pkgkey argument passed) and no other packages are installed, remove the index pattern if (!pkgName && installedPackages.length === 0) { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts index aaff5df39bac31..4ad6fc96218dea 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ElasticsearchAssetType, Installation, KibanaAssetType } from '../../../types'; +import { ElasticsearchAssetType, Installation, KibanaSavedObjectType } from '../../../types'; import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; jest.mock('./install'); @@ -41,7 +41,7 @@ const mockInstallation: SavedObject = { type: 'epm-packages', attributes: { id: 'test-pkg', - installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }], + installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }], installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], es_index_patterns: { pattern: 'pattern-name' }, name: 'test package', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts index a04bfaafe7570b..a41511260c6e72 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObject } from 'src/core/server'; -import { ElasticsearchAssetType, Installation, KibanaAssetType } from '../../../types'; +import { ElasticsearchAssetType, Installation, KibanaSavedObjectType } from '../../../types'; import { getInstallType } from './install'; const mockInstallation: SavedObject = { @@ -13,7 +13,7 @@ const mockInstallation: SavedObject = { type: 'epm-packages', attributes: { id: 'test-pkg', - installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }], + installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }], installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], es_index_patterns: { pattern: 'pattern-name' }, name: 'test packagek', @@ -30,7 +30,7 @@ const mockInstallationUpdateFail: SavedObject = { type: 'epm-packages', attributes: { id: 'test-pkg', - installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }], + installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }], installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], es_index_patterns: { pattern: 'pattern-name' }, name: 'test packagek', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 23666162e91ef4..0496a6e9aeef1e 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -18,6 +18,7 @@ import { KibanaAssetReference, EsAssetReference, InstallType, + KibanaAssetType, } from '../../../types'; import * as Registry from '../registry'; import { @@ -364,9 +365,9 @@ export async function createInstallation(options: { export const saveKibanaAssetsRefs = async ( savedObjectsClient: SavedObjectsClientContract, pkgName: string, - kibanaAssets: ArchiveAsset[] + kibanaAssets: Record ) => { - const assetRefs = kibanaAssets.map(toAssetReference); + const assetRefs = Object.values(kibanaAssets).flat().map(toAssetReference); await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { installed_kibana: assetRefs, }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index 4b4fe9540dd956..5db47adc983c2a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -12,6 +12,9 @@ import { AssetType, CallESAsCurrentUser, ElasticsearchAssetType, + EsAssetReference, + KibanaAssetReference, + Installation, } from '../../../types'; import { getInstallation, savedObjectTypes } from './index'; import { deletePipeline } from '../elasticsearch/ingest_pipeline/'; @@ -46,7 +49,7 @@ export async function removeInstallation(options: { // Delete the installed assets const installedAssets = [...installation.installed_kibana, ...installation.installed_es]; - await deleteAssets(installedAssets, savedObjectsClient, callCluster); + await deleteAssets(installation, savedObjectsClient, callCluster); // Delete the manager saved object with references to the asset objects // could also update with [] or some other state @@ -64,17 +67,20 @@ export async function removeInstallation(options: { // successful delete's in SO client return {}. return something more useful return installedAssets; } -async function deleteAssets( - installedObjects: AssetReference[], - savedObjectsClient: SavedObjectsClientContract, - callCluster: CallESAsCurrentUser + +function deleteKibanaAssets( + installedObjects: KibanaAssetReference[], + savedObjectsClient: SavedObjectsClientContract ) { - const logger = appContextService.getLogger(); - const deletePromises = installedObjects.map(async ({ id, type }) => { + return installedObjects.map(async ({ id, type }) => { + return savedObjectsClient.delete(type, id); + }); +} + +function deleteESAssets(installedObjects: EsAssetReference[], callCluster: CallESAsCurrentUser) { + return installedObjects.map(async ({ id, type }) => { const assetType = type as AssetType; - if (savedObjectTypes.includes(assetType)) { - return savedObjectsClient.delete(assetType, id); - } else if (assetType === ElasticsearchAssetType.ingestPipeline) { + if (assetType === ElasticsearchAssetType.ingestPipeline) { return deletePipeline(callCluster, id); } else if (assetType === ElasticsearchAssetType.indexTemplate) { return deleteTemplate(callCluster, id); @@ -82,8 +88,22 @@ async function deleteAssets( return deleteTransforms(callCluster, [id]); } }); +} + +async function deleteAssets( + { installed_es: installedEs, installed_kibana: installedKibana }: Installation, + savedObjectsClient: SavedObjectsClientContract, + callCluster: CallESAsCurrentUser +) { + const logger = appContextService.getLogger(); + + const deletePromises: Array> = [ + ...deleteESAssets(installedEs, callCluster), + ...deleteKibanaAssets(installedKibana, savedObjectsClient), + ]; + try { - await Promise.all([...deletePromises]); + await Promise.all(deletePromises); } catch (err) { logger.error(err); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 66f28fe58599a5..0172f3bb38f510 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -242,10 +242,12 @@ export function getAsset(key: string) { } export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByType { + const kibanaAssetTypes = Object.values(KibanaAssetType); + // ASK: best way, if any, to avoid `any`? const assets = paths.reduce((map: any, path) => { const parts = pathParts(path.replace(/^\/package\//, '')); - if (parts.type in KibanaAssetType) { + if (parts.service === 'kibana' && kibanaAssetTypes.includes(parts.type)) { if (!map[parts.service]) map[parts.service] = {}; if (!map[parts.service][parts.type]) map[parts.service][parts.type] = []; map[parts.service][parts.type].push(parts); diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 3518daa1aba631..5cf43d2830489a 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -56,6 +56,7 @@ export { AssetType, Installable, KibanaAssetType, + KibanaSavedObjectType, AssetParts, AssetsGroupedByServiceByType, CategoryId, diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts index 72ea9cb4e7ef35..8e8e4f010bcb55 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts @@ -184,6 +184,16 @@ export default function (providerContext: FtrProviderContext) { resSearch = err; } expect(resSearch.response.data.statusCode).equal(404); + let resIndexPattern; + try { + resIndexPattern = await kibanaServer.savedObjects.get({ + type: 'index-pattern', + id: 'test-*', + }); + } catch (err) { + resIndexPattern = err; + } + expect(resIndexPattern.response.data.statusCode).equal(404); }); it('should have removed the fields from the index patterns', async () => { // The reason there is an expect inside the try and inside the catch in this test case is to guard against two @@ -345,6 +355,7 @@ const expectAssetsInstalled = ({ expect(res.statusCode).equal(200); }); it('should have installed the kibana assets', async function () { + // These are installed from Fleet along with every package const resIndexPatternLogs = await kibanaServer.savedObjects.get({ type: 'index-pattern', id: 'logs-*', @@ -355,6 +366,8 @@ const expectAssetsInstalled = ({ id: 'metrics-*', }); expect(resIndexPatternMetrics.id).equal('metrics-*'); + + // These are the assets from the package const resDashboard = await kibanaServer.savedObjects.get({ type: 'dashboard', id: 'sample_dashboard', @@ -375,6 +388,22 @@ const expectAssetsInstalled = ({ id: 'sample_search', }); expect(resSearch.id).equal('sample_search'); + const resIndexPattern = await kibanaServer.savedObjects.get({ + type: 'index-pattern', + id: 'test-*', + }); + expect(resIndexPattern.id).equal('test-*'); + + let resInvalidTypeIndexPattern; + try { + resInvalidTypeIndexPattern = await kibanaServer.savedObjects.get({ + type: 'invalid-type', + id: 'invalid', + }); + } catch (err) { + resInvalidTypeIndexPattern = err; + } + expect(resInvalidTypeIndexPattern.response.data.statusCode).equal(404); }); it('should create an index pattern with the package fields', async () => { const resIndexPatternLogs = await kibanaServer.savedObjects.get({ @@ -415,6 +444,10 @@ const expectAssetsInstalled = ({ id: 'sample_dashboard2', type: 'dashboard', }, + { + id: 'test-*', + type: 'index-pattern', + }, { id: 'sample_search', type: 'search', diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts index 90dce92a2c6b56..b16cf039f0dadb 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts @@ -283,14 +283,14 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_dashboard', type: 'dashboard', }, - { - id: 'sample_search2', - type: 'search', - }, { id: 'sample_visualization', type: 'visualization', }, + { + id: 'sample_search2', + type: 'search', + }, ], installed_es: [ { diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/invalid.json b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/invalid.json new file mode 100644 index 00000000000000..bffc52ded73d65 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/invalid.json @@ -0,0 +1,11 @@ +{ + "attributes": { + "fieldFormatMap": "{}", + "fields": "[]", + "timeFieldName": "@timestamp", + "title": "invalid" + }, + "id": "invalid", + "references": [], + "type": "invalid-type" +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/test_index_pattern.json b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/test_index_pattern.json new file mode 100644 index 00000000000000..48ba36a1167093 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/index_pattern/test_index_pattern.json @@ -0,0 +1,11 @@ +{ + "attributes": { + "fieldFormatMap": "{}", + "fields": "[]", + "timeFieldName": "@timestamp", + "title": "test-*" + }, + "id": "test-*", + "references": [], + "type": "index-pattern" +} From 4a8f42603b42b731ef43e0e89eea0a33bb6aa7f8 Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 5 Nov 2020 12:18:06 -0800 Subject: [PATCH 07/81] [Enterprise Search] Fix/update MockRouter helper to return specific routes/paths (#82682) * Fix tests failing for route files that have more than 2 router registrations of the same method - This fix allows us to specify the route call we're testing via a path param * Update all existing uses of MockRouter to pass path param * Add helpful error messaging - e.g., in case a path gets typoed --- .../server/__mocks__/router.mock.ts | 18 +++++- .../routes/app_search/credentials.test.ts | 30 +++++++-- .../server/routes/app_search/engines.test.ts | 6 +- .../server/routes/app_search/settings.test.ts | 11 +++- .../enterprise_search/config_data.test.ts | 5 +- .../enterprise_search/telemetry.test.ts | 6 +- .../routes/workplace_search/groups.test.ts | 62 +++++++++++++++---- .../routes/workplace_search/overview.test.ts | 6 +- 8 files changed, 119 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts index e3471d7268cb17..f00e0f2807e8d3 100644 --- a/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts +++ b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts @@ -21,6 +21,7 @@ type PayloadType = 'params' | 'query' | 'body'; interface IMockRouterProps { method: MethodType; + path: string; payload?: PayloadType; } interface IMockRouterRequest { @@ -33,12 +34,14 @@ type TMockRouterRequest = KibanaRequest | IMockRouterRequest; export class MockRouter { public router!: jest.Mocked; public method: MethodType; + public path: string; public payload?: PayloadType; public response = httpServerMock.createResponseFactory(); - constructor({ method, payload }: IMockRouterProps) { + constructor({ method, path, payload }: IMockRouterProps) { this.createRouter(); this.method = method; + this.path = path; this.payload = payload; } @@ -47,8 +50,13 @@ export class MockRouter { }; public callRoute = async (request: TMockRouterRequest) => { - const [, handler] = this.router[this.method].mock.calls[0]; + const routerCalls = this.router[this.method].mock.calls as any[]; + if (!routerCalls.length) throw new Error('No routes registered.'); + const route = routerCalls.find(([router]: any) => router.path === this.path); + if (!route) throw new Error('No matching registered routes found - check method/path keys'); + + const [, handler] = route; const context = {} as jest.Mocked; await handler(context, httpServerMock.createKibanaRequest(request as any), this.response); }; @@ -81,7 +89,11 @@ export class MockRouter { /** * Example usage: */ -// const mockRouter = new MockRouter({ method: 'get', payload: 'body' }); +// const mockRouter = new MockRouter({ +// method: 'get', +// path: '/api/app_search/test', +// payload: 'body' +// }); // // beforeEach(() => { // jest.clearAllMocks(); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts index 357b49de934122..af498e346529a9 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts @@ -14,7 +14,11 @@ describe('credentials routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'get', payload: 'query' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/credentials', + payload: 'query', + }); registerCredentialsRoutes({ ...mockDependencies, @@ -46,7 +50,11 @@ describe('credentials routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'post', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/credentials', + payload: 'body', + }); registerCredentialsRoutes({ ...mockDependencies, @@ -155,7 +163,11 @@ describe('credentials routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'get', payload: 'query' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/credentials/details', + payload: 'query', + }); registerCredentialsRoutes({ ...mockDependencies, @@ -175,7 +187,11 @@ describe('credentials routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'put', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/app_search/credentials/{name}', + payload: 'body', + }); registerCredentialsRoutes({ ...mockDependencies, @@ -292,7 +308,11 @@ describe('credentials routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'delete', payload: 'params' }); + mockRouter = new MockRouter({ + method: 'delete', + path: '/api/app_search/credentials/{name}', + payload: 'params', + }); registerCredentialsRoutes({ ...mockDependencies, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts index cd22ff98b01cee..3bfe8abf8a2dff 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts @@ -25,7 +25,11 @@ describe('engine routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'get', payload: 'query' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines', + payload: 'query', + }); registerEnginesRoute({ ...mockDependencies, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts index 095c0ac2b6ab14..be3b2632eb67d9 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts @@ -14,7 +14,10 @@ describe('log settings routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'get' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/log_settings', + }); registerSettingsRoutes({ ...mockDependencies, @@ -36,7 +39,11 @@ describe('log settings routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'put', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/app_search/log_settings', + payload: 'body', + }); registerSettingsRoutes({ ...mockDependencies, diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts index 253c9a418d60b6..b6f449ced2599f 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts @@ -18,7 +18,10 @@ describe('Enterprise Search Config Data API', () => { let mockRouter: MockRouter; beforeEach(() => { - mockRouter = new MockRouter({ method: 'get' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/enterprise_search/config_data', + }); registerConfigDataRoute({ ...mockDependencies, diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts index bd6f4b9da91fd6..2229860d87a000 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts @@ -25,7 +25,11 @@ describe('Enterprise Search Telemetry API', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'put', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/enterprise_search/stats', + payload: 'body', + }); registerTelemetryRoute({ ...mockDependencies, diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts index 31e055565ead12..2f244022be0378 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts @@ -25,7 +25,11 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - mockRouter = new MockRouter({ method: 'get', payload: 'query' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/groups', + payload: 'query', + }); registerGroupsRoute({ ...mockDependencies, @@ -43,7 +47,11 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'post', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/groups', + payload: 'body', + }); registerGroupsRoute({ ...mockDependencies, @@ -71,7 +79,11 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'post', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/groups/search', + payload: 'body', + }); registerSearchGroupsRoute({ ...mockDependencies, @@ -141,7 +153,11 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - mockRouter = new MockRouter({ method: 'get', payload: 'params' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/groups/{id}', + payload: 'params', + }); registerGroupRoute({ ...mockDependencies, @@ -176,7 +192,11 @@ describe('groups routes', () => { }; it('creates a request handler', () => { - mockRouter = new MockRouter({ method: 'put', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/workplace_search/groups/{id}', + payload: 'body', + }); registerGroupRoute({ ...mockDependencies, @@ -204,7 +224,11 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'delete', payload: 'params' }); + mockRouter = new MockRouter({ + method: 'delete', + path: '/api/workplace_search/groups/{id}', + payload: 'params', + }); registerGroupRoute({ ...mockDependencies, @@ -227,7 +251,7 @@ describe('groups routes', () => { }); }); - describe('GET /api/workplace_search/groups/{id}/users', () => { + describe('GET /api/workplace_search/groups/{id}/group_users', () => { let mockRouter: MockRouter; beforeEach(() => { @@ -235,7 +259,11 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - mockRouter = new MockRouter({ method: 'get', payload: 'params' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/groups/{id}/group_users', + payload: 'params', + }); registerGroupUsersRoute({ ...mockDependencies, @@ -261,7 +289,11 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'post', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/groups/{id}/share', + payload: 'body', + }); registerShareGroupRoute({ ...mockDependencies, @@ -291,7 +323,11 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'post', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/groups/{id}/assign', + payload: 'body', + }); registerAssignGroupRoute({ ...mockDependencies, @@ -330,7 +366,11 @@ describe('groups routes', () => { }; it('creates a request handler', () => { - mockRouter = new MockRouter({ method: 'put', payload: 'body' }); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/workplace_search/groups/{id}/boosts', + payload: 'body', + }); registerBoostsGroupRoute({ ...mockDependencies, diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts index a387cab31c17ac..9317b1ada85afa 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts @@ -14,7 +14,11 @@ describe('Overview route', () => { beforeEach(() => { jest.clearAllMocks(); - mockRouter = new MockRouter({ method: 'get', payload: 'query' }); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/overview', + payload: 'query', + }); registerOverviewRoute({ ...mockDependencies, From ca04175ae9cb8305a153612e85b4403bf9d95a8c Mon Sep 17 00:00:00 2001 From: John Schulz Date: Thu, 5 Nov 2020 15:29:33 -0500 Subject: [PATCH 08/81] Combine related getBuffer* functions. Add tests (#82766) ## Summary Move logic from `getBufferExtractorForContentType` into `getBufferExtractor` & change the interface so one function can be used. ### Diff showing old vs new call ```diff - getBufferExtractorForContentType(contentType); + getBufferExtractor({ contentType }); ``` ```diff - getBufferExtractor(archivePath); + getBufferExtractor({ archivePath }); ``` ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../server/services/epm/archive/index.ts | 21 ++++-------- .../server/services/epm/registry/extract.ts | 24 +++++++++++-- .../services/epm/registry/index.test.ts | 34 ++++++++++++++++--- .../server/services/epm/registry/index.ts | 16 ++++----- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts index 91ed489b3a5bbe..395f9c15b3b878 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts @@ -18,7 +18,7 @@ import { import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors'; import { pkgToPkgKey } from '../registry'; import { cacheGet, cacheSet, setArchiveFilelist } from '../registry/cache'; -import { unzipBuffer, untarBuffer, ArchiveEntry } from '../registry/extract'; +import { ArchiveEntry, getBufferExtractor } from '../registry/extract'; export async function loadArchivePackage({ archiveBuffer, @@ -37,24 +37,17 @@ export async function loadArchivePackage({ }; } -function getBufferExtractorForContentType(contentType: string) { - if (contentType === 'application/gzip') { - return untarBuffer; - } else if (contentType === 'application/zip') { - return unzipBuffer; - } else { - throw new PackageUnsupportedMediaTypeError( - `Unsupported media type ${contentType}. Please use 'application/gzip' or 'application/zip'` - ); - } -} - export async function unpackArchiveToCache( archiveBuffer: Buffer, contentType: string, filter = (entry: ArchiveEntry): boolean => true ): Promise { - const bufferExtractor = getBufferExtractorForContentType(contentType); + const bufferExtractor = getBufferExtractor({ contentType }); + if (!bufferExtractor) { + throw new PackageUnsupportedMediaTypeError( + `Unsupported media type ${contentType}. Please use 'application/gzip' or 'application/zip'` + ); + } const paths: string[] = []; try { await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts index 6d029b54a63171..b79218638ce247 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts @@ -17,7 +17,7 @@ export async function untarBuffer( buffer: Buffer, filter = (entry: ArchiveEntry): boolean => true, onEntry = (entry: ArchiveEntry): void => {} -): Promise { +): Promise { const deflatedStream = bufferToStream(buffer); // use tar.list vs .extract to avoid writing to disk const inflateStream = tar.list().on('entry', (entry: tar.FileStat) => { @@ -36,7 +36,7 @@ export async function unzipBuffer( buffer: Buffer, filter = (entry: ArchiveEntry): boolean => true, onEntry = (entry: ArchiveEntry): void => {} -): Promise { +): Promise { const zipfile = await yauzlFromBuffer(buffer, { lazyEntries: true }); zipfile.readEntry(); zipfile.on('entry', async (entry: yauzl.Entry) => { @@ -50,6 +50,26 @@ export async function unzipBuffer( return new Promise((resolve, reject) => zipfile.on('end', resolve).on('error', reject)); } +type BufferExtractor = typeof unzipBuffer | typeof untarBuffer; +export function getBufferExtractor( + args: { contentType: string } | { archivePath: string } +): BufferExtractor | undefined { + if ('contentType' in args) { + if (args.contentType === 'application/gzip') { + return untarBuffer; + } else if (args.contentType === 'application/zip') { + return unzipBuffer; + } + } else if ('archivePath' in args) { + if (args.archivePath.endsWith('.zip')) { + return unzipBuffer; + } + if (args.archivePath.endsWith('.gz')) { + return untarBuffer; + } + } +} + function yauzlFromBuffer(buffer: Buffer, opts: yauzl.Options): Promise { return new Promise((resolve, reject) => yauzl.fromBuffer(buffer, opts, (err?: Error, handle?: yauzl.ZipFile) => diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts index ba51636c13f369..a2d5c8147002de 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts @@ -82,14 +82,38 @@ describe('splitPkgKey tests', () => { }); }); -describe('getBufferExtractor', () => { - it('returns unzipBuffer if the archive key ends in .zip', () => { - const extractor = getBufferExtractor('.zip'); +describe('getBufferExtractor called with { archivePath }', () => { + it('returns unzipBuffer if `archivePath` ends in .zip', () => { + const extractor = getBufferExtractor({ archivePath: '.zip' }); expect(extractor).toBe(unzipBuffer); }); - it('returns untarBuffer if the key ends in anything else', () => { - const extractor = getBufferExtractor('.xyz'); + it('returns untarBuffer if `archivePath` ends in .gz', () => { + const extractor = getBufferExtractor({ archivePath: '.gz' }); expect(extractor).toBe(untarBuffer); + const extractor2 = getBufferExtractor({ archivePath: '.tar.gz' }); + expect(extractor2).toBe(untarBuffer); + }); + + it('returns `undefined` if `archivePath` ends in anything else', () => { + const extractor = getBufferExtractor({ archivePath: '.xyz' }); + expect(extractor).toEqual(undefined); + }); +}); + +describe('getBufferExtractor called with { contentType }', () => { + it('returns unzipBuffer if `contentType` is `application/zip`', () => { + const extractor = getBufferExtractor({ contentType: 'application/zip' }); + expect(extractor).toBe(unzipBuffer); + }); + + it('returns untarBuffer if `contentType` is `application/gzip`', () => { + const extractor = getBufferExtractor({ contentType: 'application/gzip' }); + expect(extractor).toBe(untarBuffer); + }); + + it('returns `undefined` if `contentType` ends in anything else', () => { + const extractor = getBufferExtractor({ contentType: '.xyz' }); + expect(extractor).toEqual(undefined); }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 0172f3bb38f510..e6d14a7846c225 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -26,14 +26,14 @@ import { setArchiveFilelist, deleteArchiveFilelist, } from './cache'; -import { ArchiveEntry, untarBuffer, unzipBuffer } from './extract'; +import { ArchiveEntry, getBufferExtractor } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; import { getRegistryUrl } from './registry_url'; import { appContextService } from '../..'; import { PackageNotFoundError, PackageCacheError } from '../../../errors'; -export { ArchiveEntry } from './extract'; +export { ArchiveEntry, getBufferExtractor } from './extract'; export interface SearchParams { category?: CategoryId; @@ -139,7 +139,10 @@ export async function unpackRegistryPackageToCache( ): Promise { const paths: string[] = []; const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion); - const bufferExtractor = getBufferExtractor(archivePath); + const bufferExtractor = getBufferExtractor({ archivePath }); + if (!bufferExtractor) { + throw new Error('Unknown compression format. Please use .zip or .gz'); + } await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { const { path, buffer } = entry; const { file } = pathParts(path); @@ -199,13 +202,6 @@ export function pathParts(path: string): AssetParts { } as AssetParts; } -export function getBufferExtractor(archivePath: string) { - const isZip = archivePath.endsWith('.zip'); - const bufferExtractor = isZip ? unzipBuffer : untarBuffer; - - return bufferExtractor; -} - export async function ensureCachedArchiveInfo( name: string, version: string, From 2287376aebb58ffb127b19193bbb5255a2614dd1 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Thu, 5 Nov 2020 12:37:58 -0800 Subject: [PATCH 09/81] [DOCS] Updates field formatters (#82667) * [DOCS] Updates field formatters * Update docs/management/managing-fields.asciidoc Co-authored-by: Kaarina Tungseth * [DOCS] Minor edits Co-authored-by: Kaarina Tungseth --- .../images/management-index-patterns.png | Bin 53215 -> 0 bytes docs/management/managing-fields.asciidoc | 75 ++++-------------- 2 files changed, 17 insertions(+), 58 deletions(-) delete mode 100644 docs/management/images/management-index-patterns.png diff --git a/docs/management/images/management-index-patterns.png b/docs/management/images/management-index-patterns.png deleted file mode 100644 index 232d32893b96d3e6a0ae7775fc0cdde40a1a4bc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53215 zcmeFZQ*@=xx&|5>Gq%;SZJQn2c6V&swma;oW83c7w(Xp(z0Ufv*MD~|&dna<8)Me^ z=B!zGQC08rycMP(Ck_vT4Fd!O1TQHeq67p41^@yA&W3{cd_#M1{qgw${7p$*2&i%b z_Xr3`07z0qP}vpuGz(H6wI5?Z&pkju;*j@Nw~&OG5KmN8G_Vs~K?HynOB}8bT%#|D zBuc3yQdBOHwC-`y9uyl31bsC8;gO)-rj3lO>Da}dzPfgPdOBv8GTLFc?y&j*d(S{9ljXOR8^cM#S#j6|y`cyKpxV7daad$8EYcbOeCFPX6lB z9xdbr6A&T?LMrgTp9gN>JC@MU(BxR^g+A_(klo=u%89Lo{76@YirAQ@)|S`24r%s?#?y?9KJari^FRvOoE0(w((2&g8lbX0bQb+n=S~b0~H^4+k{r} z3;Ks2eO9xEE-}qr_A59;@FxH7vHt6g76>Z9I_#1e`g8vN_ai{{`K1!k7Q{bx`TuG{ z$Nt|8{eRV=i3Xgg=-!=dB*Tpc9%P zRtONof$(N~0fQrw8~a7Ix4Wo-T2yv!01G=9)20k*}x#p^tT5>^+8OWBy z71Qoyz)W>w%=-W(Fl%sez(8q)XHfjDmY+w6z-7HTryHjd*Z>^tr+%TM__B7M2)QO_CxQqiZ|@KP z%@=I069G~Pk$o=ULP&`GTB8NDLEe!}4f1?rgR0%A{${mJDNv-wHCK-GH9P7On+<>= z%{PHBgsaKr17d&s%YS>D&2@4C;H~wJ4qUvyd4*L@5DlIew*!ctWXVli@>r)2043q- z_yhFIKlwuq5yBO8D_+;bSQ?EsWf(L%(wIgc8C<+y;-gKTPqkxN+GUv#qpP_Qc8Aev^rx-8W4|@_#wiE;=aV z4ByS&T~d7f^X>iP&_d?(US`x$LUZ=5GhP2RS~mFngm#OMNv) zgGDYE?EU?7q5~fssxYWuVoiB%+c-fEgo%w$jz%4yl>)(Z!;lH+)rJqw$XO%YrI%hg z!NSMKS;q?goOMEK=)-V?n#XBzjGM>&&dVj_^|MS~{85cC-veS`4~keD5dZc7gAXQh z0166q2qHlo3Wc16h=@oFq==Z<$U7+HX3-|yG$Z5D{7y6uN%f9#iD&z36U`#d7rzve zD22_lBVsk;E)F(b$6fmbE;2{SjUVqb4N~4P(Con}^?%GoZM=X?+vBc%99z=04Ayn};7hiyCe;+#j>5oRc84lN%s0SyNY z!lTFKmp;GZa*RYKa7PJS7qez$G>QYFFEoqa1Wj%E>CZaWzD&;1Qt9!2mAu{`(~ZgT zI2N*J_+tTXloM!2&>wOsQ&CvAP!SU%HZhQsTD*dXm~5|z)Z@6y;4oz8fmka>(zU6XG3XTC1qge z08VkI+-?IB7EY&WtHzI_HM+dP1-ADyKaSQGFUPhO1H5QOWa%6XEUGw$@z*Rg1BZ^o z8FH1Sx_WA0VDPrCQ*4{tO?Y|v2|N*0e+>~)9)oLEAL5|Ls__K)Z-iyXAi>j<49G1+ ze2aVGK|lyS1%)Sc>6K?YG7e5ei^D$OynXmsVrr`SSG|=6(?SFA=d+!i1o5`^Z>B#= zcexfSbrLo=^*uv&9#@+k+FY*rPS|T|9FPeJQZhdT9z?~(GfsXyA<*~>T3f>%LmzL! zV)gIJghs|=l2$~@E3nCl`7&_-ek^)x{(cmH_~hy7C|#-DJX3PFL4FpH-K#k@b%GYi z$jQwq-R%!9QwafUcXD&$BB5X(8yFHKEoa|4ZJdrl4VN%1v=1P_^>g~w@OE0N*;QI1>wX*zMk}0QA?*H)+uO*-tZS&cavKP8GdX?abXg`i0T`9U}IL(H0cf68a?ikk|S8 zAEzAOG+N}>oLs`81Tsd)q1znRdW}eD9IFf}ju(}H47zHX}^YSbDplQ3(c7$4Q{FIHqHH=# zGTn!V+(;r_R(@Jg1p4SuTuxYVak1z7l0>O)yJq-XGNO1ieggZ(DTSb5_YZ!0mkZ0K zr1O&oo0YvgK0C=hsU+jmMLQ8;u#Wdis5d2LE!^pTzX-xBjy~wYb+B zW+igiFXYphUGN>Tz(TQCEdxCc#we4@mE%90j%cE=S!K{E>xlL)dl7e{fO7oGFy{B+B)Y zst38lvn9T12QeynM`8Qm3@Y|#u~3mcor@dTb~g#ywbc9S`E@_DN}&>w-4UBfaT|+M z$k>@8_fwPB5p2xoR_6abefVh+167SIWF-1N2pU+tt&uMwB7%-RhcT{n>1ejF(~xI*FA-7S`>|p|i)0wO z@fsglIT`D1vbx>A$Sxg386N1L$q6V3E9~sev3jqy+%XyMepza^iea3GMWg!Cof@Yq zY&RIKQ)}2Sh4t27d_6=W`_(`d(N{6Yn{LDNhF2uiY9N+gvkuC%Z^z`}oVV8EH+o&o z*LD0Uj1BIi2&fjW-dLB75I@e|vFew{$IyFWqxXk%Zo-v@i^Q~F`y3tg|k7Ett;8`itIwV z!sGQEdD`vz9!NO-wpYpII<>O+u|d-rj-MX0wb|WU>-`3i#^P3H9W)hB0=fE3#G%vY zW;I)4s_F;T1_<@AY@GGX4fcE+ciCiK&E%aH?nWc-Q~ZW#yWG^Tje}-UFKqCoAH70MAw*JW=$Cno@jex;>8Q--T9?@dE2_t<+_H_hcQhq|#b(y|+IF9KYw5_l zbvFMzT=2*CLxNBkBvqeEjUnXLr!eumACXyKK%p6qY~f-_U`*txg5o=F15XX5Q`#W& zIEL#0rCyh`*d}N3P2pKuqY;tnt>wO5UjPsf%H6w73{6lTJ~|%#dj~#%TuL$~BzzPT z3QkK-Cpkev$p$8y5k>C`=YYLGJ^tu`Uy~ru zw6-dFV$Tenx<#t^_0yfJ4OP26Y~}~tz@q-GJC6(ZP=EO)#C*###5;R`oxvQzTFxz_ zTc^8_`m--zKdGCTx^9A|l(pIeSq%3P&pbZyzPfee`8Oqx{kGvj^2YSXfm_Z}f(E!- z%$1$QEOhBk+MPOWX4f0b9)~;g=}QEZiF`x&WmWkH!;WlZ+#t629&M$%DjOWJXKIVc z`PJ%ShWQ(?kPU@|#h1~+gU@2;vyAmQ$ zHG?Kv-8Pv4Pwsilq}~u8IJ-b9@`~>CE49=(0$z8xNM{N+m|(9M{I&j0V`%N)V3=tk zyDN5`?99{I+4u~;<5~J%O>R}hA{*YIdk-6CSQyWhI(hYI-wx<*cRVy& zowII_elLv0H(F1x;$}b!^;SQG41LSeOY}*pV)->Y>FmkX&#BCj3Vubz-UA-o@qiE* zCPaB({2&PLY_jVM^cYhEwx{RwMv}$ATxCR~n4g+f!(aW8TXHZEj#h4bIE5HVwD#0& zmXI;Kbj`kgN-v%A9#Qi&baR=1xOjy{soj)&CBWfvpZUu1ZkERR4WSJeSxxxlFm+73c66Tl4we>uZa%$Mog-bOHPGDU4n0m1!nb z_HLkqosRdDNjh90Wl%Ts&}Y6k9mH0Ub{XSUkPM*n!N*a2Jkz= z=BDx~FJm}(#?2fZ*f1gc=yMCo(*LAp%)PbKmGTbI%56C~y654vrGXn_>CyXRFGgz5 z4b_F|@l(-31CGRdt~<9+L%-a4?p@lXJ5Eo>i7}W z&Y*g4{LC~vD4&7(1G$X};~dUc+Fx#fgK&&lTQ{p?6bgELtA8m^3>Yg)`>WSVVqEgx z%}Ae9aiLBa-}>!MhlkJVjDA_M|1fR$%FB7 zeMDo+x6rCBRl5=WeG5*-2|9F~lo|>#27tBZV0Ft12x||&*2m5)62gWA||Ak*BqK&HR1 z)ZU4%rQo2au&G?9wTRON4nNix?`;S-Rhrk*4)B+#SZFwLsIR}B#FCbr1)XfbtUxw= zA;V#mWP{qGri^srg+6Zk-4e-i{Sb%981}=h6QAzFtfdJx8+1gVSDvP#egiS(F3I*T zLaJGaQ?~t1oZjzFk`os|?!EbFlTtPVwV*s|rvHWzSio+2kpe?ST>phOdQ*$8Go|8i zLBW~G%`Aui9-9$FDS$__-S_)vTm@$TDs~i#H4R=q2^yzcwwKU8i<;=5&*c*E?(`dr z63j1OXAY!^e!Gymhc`X(KCYPH3+cs&m&CFWuIv^iF8E~Z9;E+n`Q9MeA+`|tOpT7<)T0!Nm+L^-=ClCqq{4>#=%fuLf->G`~)g*>0T%?0klBn_8`2 z-_-QHf~!OB=UbyFguI%{PLPARnBTi0Neam$$J@NUe`HVa@PBxk1!8<2mqX?wf zNL2`*ZhuXSeK}ffPH;GopLT^H$TXn2*zf_$hSCD?O?~Biw#Nhu>Z7OPRW|E8nJXvZ z)jJ7b91BUku4l1bl?X?rv)0sYb#AZFtrz2AjNsqiK`fL`k)xbAJUO8@>UdJB)QO1Y ze@`Xr6ckY;65}kr`pK`Gtt&P(we-;w1S?sSFZ#7E4Hc0)@|_UElk7_i5JS!BI! z6&pujOvHKu-F)P6^uzeJeGi_lea=3A^nX2^E|PNeaO_1Z+xL=>22Jo1CHb5?*l;dH|lgzx`m#p<2i35 zpflBxRZ9+(G*Xs%K6sYsLhF@lOx|eRJ74(}1N=U)) z@=bWJ~jqwY_((o{v^#xIJ*+yGz!xJ}MZ($5lF)^_Z+37PG<;Pb?5IH*ZAlPZkF zqu{Rk90l!~vNhNmWSFNL72;nYU5PJ5Z6oC)V3^%%{524?6WW>xB6i*tJVrprSojB2 zLg~2F#RKPk7ArH`Kqqx+i2$LFXcJ*C#8oWdlXNI95~N_Hdi3lT+7pNi3*tP4a-~D4 zj_?Mn0GQ*%F`MI*fKX*HkidI+;V7KV4PWxs)5jatus{TZbBAx}FK;H_8kgk7=ZbXA z`hWI=o~+kftSK!w*w~nsH(o3nv`hbx&mlVgQRxN=fruX~#8a(2i}i}g@2MA#N@=Zq zO}j!{l`dlk5~+;F=-xh0lH{siT6}&*BJK&*+mw1Z_G{u%D}|lm*H3)pWACb8%gIaz z+r~z(DO4&_!EMY8L05LA-`dsYU}m`sq>@IM%=JH5Vio}5F$`kxR#Uiun}fHsRpYm3 zCn6Y`gh@WUIH}JL%=idoaq*H&0N^LGd5w^eicNi_Kf>On72C8gai3NR1~Zw9s>JhpSp0Gf)9U=| z=zWHlidswgW4sTX&lj7~TsD(E70*(w5wTEE=7y}4@W*`Gj9oANr3ZT~euv(aeW9qI zMnr!eQ%?zf2gN&aZ7)l{Ck6m-4Vd5cy?`)m-%=2GJND)}`vaRV{iw1$#lk^BAFE_m z1GGTXOUuBRs9&zC!PdNJnv$3$hJ%;F@Ic(Um4DC#j-B^^LzXkX^5sB5ru;cz9ZN-` zDtQ3CQq+`_;`yX(!i=+l#)(qqRX6i0Gid#tHWWda(W-L5hYvHf9XloVOW&GHdbKP* zX0udT!TE*OgcKT2u~Q(FJ+>|sJf9A=PjAWm^)rFTu3wDWu+-RmImI26Yj&9{gGsMN z>Gc*&>HC$`5jY|gYxEot`*4kc`3m|PaulD))?lq#JN+!l7R$g_k6p{su$%8$ZEKiK zyx3?7`3%2+vi^VD5ga&iT*aO2S^wou5 z0=(%1AcgL>n|Isi8IBq0^k{&eO%sqcRovxP(vqyt4TW}7kawX)Xd-)g>ku#7)?9e=%*vcq5sps;hR-mE-YOv=Ro>ZX0&9j!K?LjrBP2RiHhjzV9pBA$8)ACj(PzWTp5!`Z&cwsPNKn6Q`C;M#Ki5EjdeQhvUPTrbuJh8`Ou9L zRf1@&TrS@8t^(GJ)ziDa)w8l)3kBzP(>yi>>q_!^D!F9}d7q~PI8lzQ{B?wu?GSg? zOg~>k%44ss?PEiAe4&Y;Td7|^WD9S;Wo>4~9p1(1wvSma>;>JfbQ-Vs*$3W6V};CN zX>LYL_YNy|2w(TDJ_=juTnk%_`_Xio<<8dr3AqY10DSX`nCqaAk;0*oS!X(YYnq)D zNS2tYzUV=rYtpH^nf<`# zhSN$=kk|V;@&b!6Obmq>5~ocMfY|s|4-<}PI>oQyDnd@jPd*q>IZV{l+6jIo_|O<4 zcor?*nRaZqg?z+sPmkQJ&B zfEI#uobh`N3JTxV*%SFqt@q7lcVgM=IXttUHNEvNGg9+AX$|lj{B%A5EX2$TE&i*7 z=@ap_`l%n#H*8&W=J}p^zpZmvh#0~`flR!qjhk!8z=W2|%s14Y!3^2AJ>0;^&lr19 z#g$&`QQM1cze5^AuvynW5ZtTxp%w=Z*s|&b_0uLN0M6FNdy!a$i0<&F8hgJufY`V) zxGYzhCe<7TqBnyWjyNot(Dj=+lnBc?1-vj65Te)m{AA0T>$PDfJrSu;P{KL@{C)j9 z`c72r&(vyhU^XA&|-XFuNAh%y{ zn$WYm1D57aKfP?d7|`G&?>t{^UJ1ld9<)RRCt@*H#%8IoGrVzf)FWu#BQqnSgRW>c z$4rIYk_(OPlXca)mFuM{{4AHaGZ>TRi7pz;aFr*9XzB{1<0s53{#`V(t1A~3q9vG| zbRA>CPAnyE_7%KRor2*5ObeD1qG_f($Z}1f6$5!KhWni6KJ&xZ4(0{cR5fUL>1xgQs#%f({)Zb!9~bfW~N zt11RlJX!7$trfXsuLNRXBO;k$$`Lyb)&GL{wnAwKPnQl<3=~3=U~Nu!*>jd{`=ZNy zNA3S552h-y7@GQoDw^|gg%9#Kz$-7(J8S#!F<4W3|12|q7?TEgF)vz3ogjF7)4XtaiWeI0b*V{%e0Ma~Ji zU9rj_C;Hp(VFe4AMpss$Dz)owE9HpBkc8P@Fh$31Xd|N9lIe=kEQ)f(VYC^af`r&q zacM=GmHZTj@ zGixY!cUN*ZvrJjG**lSz!fp8-ic~b>XgqFP)#ND^C}qIa-6%=X+gp+4+z7c^K=y@P z(p2myd89~+ZzhwId&{=p-s^~bKVH@oB5XjoO9C0Qk(AwHJrdu|NmXa6NGi5ejn@mp zueu-Mn&}?`DSNZKsWH6EY~#4N?8|#nL#ZeJ{KTV(;L_c6zs%hL!#00aG%ctupiZawp2i`mq@;uJDjwnU{R# zPyz0nuK~ksMJ6ukRpu^e+V^uOd9276r6+IA(y2j>wm1WaQ}=tjcjROysH$$5Fc?oP zu%{xda`kPjR#mT{F5d?WvA(m?u_Ewck19Y22bwD~Rv{rqciVg8onJuKaQS67VjDgz zAo?!+>_?Wp5g{&~E5bLydo<7ID=vILzOk^5H?vpZXoIX6jFZz!TdJ5`Twq|kJN*I% zJcx)7Fm(cI2LSM1Y4U0$O$965t#VJrLPc9IVoAEo2pA<_getp-7S1kzc%hg3vi8G3 zwJ`Y+v{3~U`E*M|c|A^&%9p2vQQXW8(|;01njy6dzvIiJ9OqSxl1T2CM@gshfVZ2T ziZP9qJK`JA6~zWR9>j*sh}f`%vB+6Wlyov!%VuJQLvzsTklQ5S6<3A|&v2;pDb5k% zJ|uH>87qJyx(Drj-?Z#*qvoEQ*#I0{DbL8k#RX-d zP~%}|P<|+azMY2K`P9ELFrq|I8_|e$3s@9Zx})tW5;DY{sLu8cb5_}Ncs(zlIGX7y zz?{*16Y5t*(Z0)2$(IXA{E{@qdhP~2QGY%154f19f?9`Oa*amY_N(>9UmiI$nsp+o zmD;pc*%#KTfz|gC&1ncKB0tok)@Js;1vf;=eS;pzSNQg=`%N-otWb@dL^5gs_jrbp z=FjkOtPU9RCCy9k@mHoTpE={6=ilVf>+WotYoiG|uRpn=jn3+SygPzlU2xUJQ|@3H z24Y|Rieu0Hx<-60JVKG(zm-;^6k0b=_FRpcCkH_*=-Boh_PHLe@|B*DuEQzF zlgWFWGNDhNS2V&f*d0{}jRkmHeo+^87_nYP*F_55nlGZGM}@#pEC!-qi9N@hA|p(U zwE+pzkHa%-EjnH*sBJ4~ljcR9xKLFT{r0MNa6}3rP}=kP!htg;^1XDGR4Iq3m-bl0 z0ZA8cMW2+te*|T6fs(iY1Hb!;a9>}~j+9S^?$Hyb^7(}bKe1ONgjK>p6S*mU{wM8rAl6ka6eJscXc$&tu+o_-6=geci*`-(=c7?C~0>TZOh-S6TI9hIGxOAd@Bb2T>b^YO*^n9tenZbN%AOYVS=YyIU z3r#F2!+@+3!S){ODI$F@8IiThV!r`7yu|bZE_?&ShQU{ar&AcIV@4O{R7~ba5r+l=IC28M z=D~eoUg=3pI_VvEJUxOvyKihoaphgo4zx6v}TZ-84oO6C2cEUE#LXHLf>Gw_p-Qit#doXFd% zy5S806j}dtNTXJ=(6*#JCnAyMdUD|_9_6I7ym!k1SfD@n4UX{B(6mvl-Niec(X`9? zs!2S)yBit#Vzu^OJX!e8kMHrni{(h{1X9CB8&6xO7y2Voh6tF=GLk@p5pgUZzxaRvK!9ny+)g?+Z z1P@m75E1l9$V*txZ^vhEH$xN5_IHjJ;xPgDgz&{^@F%=Q?1Ag`Gpm90h3)}}`7~VB zA%svdf0u^uDc71$f?I?sz_9wt3a(i&2;@{KDjbub)^~KqXb<#Fp+Z3s&~sTLi{1Ch zJj7hiSEK3#ZAbCnkE}CGNA#}}*A-E68140)J>r2CxDTzq)jsRTvh=Ha zuuvJOPGB}{R$o;<;*9bgBS-6O4l~xtVjaD$WV3*SN!xu4Si;C}X*^=N;(r3|Vbjvl z`cI;vGMANWpz?t(2Y9gtG06#&*_Fn}hgDI7Lef~e=?}>FYW1keH<$5cl$g|yYvpX?0 z+tiN(J@N&=In$d4-S!q!xBm82*=%+@KGT$(Uq9owZ~Ouv`+L`P!ZE0`g@BalL*q#J zSqF><&9dY7$1|{ml~UwiO~+iWm&^)^1OuMQZTUn4(q0+9?c=(g)-;$ipV2Ga5vGEY zs75Vl)?4=c?)kkdsBi&uXIP?K^XfEN=YGfat0#!=#kD>E%tVI}L{TQ+2E~;pCM{Rn%iStJz1VUq%~s< zRQ|&CjrE5d>RSdFK#Xw`k{AdObTcI_<6Dk~41qFYhBQr%amC~V(e7ZxWSJ>nV2z!I#V zn+>vju%=)1Fp1{soBAzxrG;k)r3uoK1Va?d6;LrCW!f@ZsCo6ZT4sk!W?-(Lb5$`aQ(olh;Kk09>p!s+=zFe3GH)_gZ}f^+o?$@rzLA2itE=+O$rPKuaBJG z>+xe0$G&IXp8K3F#Toz+a|@4I`T|ok(u>=-NoXj`FZ?&R3ANvkI>`@wTjKbic6Th0 zb0SU9*zMNSe;BBt4jBA0MI0@Z{AxVbxFNkW5JmU}RrXg_La|)8XnRIrVBo)KWKstQ z*|L@Freo?$HCBzN)T+v#*pfb++uGCfN!4)Y2VDy9W&G4-FntGRQXBo#Cfqs)H}`0K z(WvqJYiE(yi}>$~x8IW}vL*iR_&mwV;-X;iO9}-(IPo9#LXLxV;Vs+p8Em{dBswT` zWl(u;m=i|BgSX+8nz{U+d~*?Dv2YCf?EJE3Q-B&ey#{#%27SNldpt$9c}{-xUO@0y zIGlST9`^m=9eEz|4Nq2V8?!T;3TZ6_oO`6c+#gIX6EeS^gNuCTt5zE9dc`4!{Zz=l zzcv<>T=BPx(;kH{)~Eqmi)~fPBu?U4CwuBglvqZq$YnBNVuA>+AO|trki0{zG)}0V zXjLAi{v_ou$pZzObHLT}`JAFSxm?WWmUtp8xQXOtNXB#UnCbd4zG7Spv@ zIFn>UHjv0L>9SUOr3AwtqB5L9-WaXXH5@s5Qh?SF45q%#+jI-G!T1>szem)98DRb} z=hjs7sXK&iIO}tBwtNSP>Qn2*;a_nI>nnWFRN2rI(+w#~#bXD4Sq?s5@W=SAcQ-VZ z<&M9WlI*IFFvS84U5luT`=f+0!YEo0h+&5{CxOcF1v>I35UY4zdO7+AhaSK>Q(wXx zdGMYEpc{MoIT(6>->a|@(-&lMcL68FK2hRV@g~P7H;9uk)a~AyO&yib34QOsqsvT= z$4_;{K_mJS<$Ryr3Q`4k_6?aLbF#XD4{~|N!xlU_SUV0VPwd6mAnL(X6mS$!lS4b)6C}NhN7G&MtCNaYJ+dh@)QahCVZ-?G5-~2YDwxrdRovG} zv7o`Hv(b1w-Dyb2UZACO^(!AHAF3qUiMm<>tyaTGZubS&g2$cr)IE(!?|^KmUylDv z-!v4&`#TwJt4yX(=eymoRaLuR$15GJ7N36(`tS?0d%=KDo)GvJ?-tG5I=(ToSXpRp zPQ5wJdm`m;li3pbvBw?~@J=xz6MtM|IMDW}k}C|s%5Sd!$+Atw!)UWaEqY#13R z2*}=vU599nn8YV^6vggu6kjZu!S<67n+KxXFc=P(MWoV@H3Ff@6dPHe(rhxDdGO%Q$@M1z+2O2llwnoaZij1sIAL%`w)r#lB=W-ogh%b2=|Q_Z zdHl-D{-pyzMMP&Mne%L4!$oC;lu*Cf3PkBk7I@w3Rl%}dGtpym%F_`kw;dt1R>Vcb zck$M+S12>%-aU-Bhw~pIFEDa7B*=5-?lvI~14`T0BH3`z=?7n^tV@8P4;u3zuxn*l zBMqgw&F+Tv#$COI!;-oL29arOTtnG8bB+u}g2h!#_f1}XW+5GNMX-#fCs`qKXBF8IVN7Q7&2xvAv%Hl8WDE&5CVHcaQx9YC$ z{w5tG=)OX0v%Gb}OG#K#{y2n0Afcd8eZ_EiBgUV`afDH-*v=KX!m(8AgcI3mqtsxH zg~jzf6b|zep+VTImhzbOaxNujG{f#LM6g{q`)E|_xl{GR+a$^Vhe`E7rANS;qTv>TKh!&jDl z=C$P4ySKOZwOEeswdLf(UJ`zLBxbJdTg!7Foh$F@YW-j+XhE<`5YGNUbAam~`~q*XwGW*B~rD z9)AN+KRMX1PrI#lola)0Gfqy|LAwI18rkk67_9cyGMI?8%Sev>Xb8&g_oRFskqLQ zAg=`9+xNQ20Q*|YIqcGy*iQiSMrt!Cq89pz?EZAgDt?Lw-Y(T_Q^%n-f<1W}E7~}0 z4NzGPH}U||x02P(Rrivc>~267?0Bhs80GbI(?J2tAM`Ki$0DyEX@H8^HhuYM^MoFj z7DDGH@q}XzlCOF06Qr|2JPqno$IJ}hIWw^{s>#M<>R0W7E8@*QNfGao$Ay4;&5FC<^E(y%74rC-G3)9fwTpPlO zyk9;;@yYajADYQk>V8E}s>7eD05UEt7Xork#f?6vikMqf*d#NjT~(A{i?MoV7SvM6 zPwgBL)$cWQ=a{#$Tp)(X`+k>~T&*oSFx|EjC9h$1SR#akFh*ag4{!A%@+a4;i`n0$ z*K^QK@0APqftafLbgjI}T#KAQI3X3!1a_N zC5=geJg;S;=OEx^#{x(-a6u>inaw6fO1~D%wi^w~b>Z||(7yK)sHfb{POdl^HvA>w z%_STtyV#s&owCVAvGtP84RN@a|8-EX$1pBJ0GvG8nqss1WOE7ZBj0;_Z!5hh3mO`l z5u$itCxu3XcnNcM>84;XS`uX2d3Nu^J?x-T7d~N!LM}8#7N-GpTK!$v{ujRwoq+b5 zc2Qy{Khqw+{^}0Oq{}K4RuL0NPRlU?pChX68^$sLlY`|?7@FUF4emDpha)wuqtTN^DHuS+BM}^XSGOzvCt|05VK> zapSSi*W1N~wmL3AWBlCjj=Q?#cyGM6`y{{7-A!EvG<_0f+F4R!lm|TS&y##_fXQ)% z+W|1d#syUIwC)EoYnAs0P?b^SQ8XI0BH>ezYi2tB0Rc7Jy_`EldChc{YH~SzM#&?y zA~9~)uN0x`(Te9>=QqofR$Gs{--Lw5dVb_kg;xK8J;rvm%jgprPEf~aM};MNxFzCh zH6SrOpm~>;^u?zG1tvk(SnnTq+|^u6Fu}z5>bP=A)XEA-@VeU$)A-ea1&-m5y8js> z{Y)9RZ-r72Xwo5FLV#Wc^Ros34j~;;7Q#aQZ1F#^XKdG-m@i1rn{z*>?w@PSA35ty z(4X1awjkJue+Ih#Br%0c)eYYBNjY&xDzk=01ZxDb7X0*sGdBoE3 zF770qFReglXZ^KG2gT;z=@-6AIb>ukmtwWJ)(Z_3Am~D3pu1dOv-$K}I0FeMv?jm5 zRPhBvIB~xH&s_i#gv?+t`_3uPWR4N~oSdKe`hFD@Q!R$NnX^Qrax%a~qY5`7f1lh7 zfP{nufZdg#ZLO|p_r8b598M&#;=Z`U#om#WG}aB|>M;ZzjY4+xAdaH^0OPGORfWvz z4X@TxnFsMt!1-UY=*lA7+$406IA8l@+{t|Ckv(}XNiI2(%T)FeX8tSRelr^BOZ;!q zW!2jxVA|LImC?CFXN|pp&%D`a9aT1&KX=TZJ$#@8W>v=}Bn~Y|VEGvYq78FzD3*n+RiZ|&W(*2*p z5H2tpAQKk}?d+78Z(UbuZB*y`@Dbc5;jaC z`iKE>V4?_t8-%U+?5PJl<)ARg5wgOSKm-f~ z`W7K|KPWHzC&tz#^tbW-OCe@|D3Xv1{=|W#f@BAegN*BfM;@}=>YCw&fkH?2{4?L_ ze|q0!0b=Sn%OvXl`{$*=r*eCccbVXxJsnru{jF#JzG6Y20_<}NhJ*Ck|AbKgNZ{?t zhF17Yz%K9Yi2FmY|0wuo0djryDKPxk{xF-r6g)nYoV&BD5MKVC_Wkc3I1BlL_-AQD zIS2erKmXl>(=;G(FpmzdQ=Gpm&HpsK2n`Ga%aOTW$7fwWt3#(Enc~+Qmuz z;;<(bC^Fa=&SAW#;YRll5^5TRA`NY7;fQE#6iT3eB^L&a2g=YsXNuCryXiD*1Om z@jnK=B)M6M0l>w@75Qr%?+=Ui1v|$DKHy%s&9YirqEvhcjSfp{YT|JC{v8zq1H&OO z8i0{Q___6QIp4MZud5?I?ekn1#3ji$SI93MOsnB6*5_S=xYhY0Nj((m*iWHC9m!(3 zu%^y@T6uL%n~aU_ov$qum+7B`iUr~l<+}-XMMz96)Pcn;r&^^$kH}_C*skj#3WGXC zuU4fUm(_YdbgTCf`{eUZashWv{Z1C~Riuge0Z!|GIlND|(}W1kamwY&*4b!w{2^@~wNf;j&Z+#|=#1^f-q7HBj?!X+Mwx%YtHJVUZ|KGsaW6 zM04{ET!xx+I&(>!hm(d|HhnNT1!wa62#Wcz5o=&hF@T>C(96qXT{;t3D0Egvz8bUe zpcC6k`-@Uw9w;g*I&MxeYP8!%Sg&;qLSeP&&}%zmjDyygZ5VL6@BxVMou~3nA$o2S z{!o+u6jq%G1uODUiYLK_X0d)v#{jt!aY*s3QfOyp70Hg0&)jZ7u!$R=iR0O+yB5kk zIW^3ds_1al*->6KGOSme@lO7?U$<`rRQmt}qa0f;*d*qEqUZHxNi%4OG+jGLTAN$v zbf07kF?3o@avM$7#?zK9HP(sV|A)PA{LgdS+HI^hwrwkfePXJ(?r5<-o(Jn&;78eNBA&m^7wL!nZq`0R*Hr2VzrV25LNiX=_^yh8NlYuqvF z#`9@uN(kTxa#UoEyEGv`qQbGTl*o40$6wP~CT505MkY3#I05*%kML87B+szeF@ymy z;;*et-yPFWS3P(mb1hIH#gXaEA|0$n!};YNc?Uv>hNM$>Zr%4re@dvmEGkq6VzmaB z{~q!D@;+S_&~s>@t??r}`J#J}l)%Eedq6fIx_MC*s>5B){f3u!`1bp|(ySvV=W5K) z?|snpHXagFJmpIcno?mW_hDzf|Jzj&_-)f%x$>w04fqOUZ_>^gK+Z+ zkur?|&YQ$!(wQ-MWRuZjNmZg(`T-=!`n|aYRC`_-9HLy#^d`XJU|}lgIa|vOkUL^I zxxAdV*e~?PJ}+(h8M|W#WRsK9*AKm31!6nkKen!vh1;ziZJB%f2WdEngOZ9sA#{2l zRffZ~GGkpE%8D*+e_Hvwi~QkKOChpWfrPZQWa;GhpX}x!lJQ~q_>e}^n#!wU3U ze#qyk3$eNH`Zs6uc(`_w8;K`<&VkqQVv$P}KrdG62)$C5wc+i6(>_~Q3q_$YRIXU` z&5w|H(;_-kXG&0}!A#@)=*7qT(|AO@UPBBTjZ&8romQuXvdZ2=T0DWAlCOv0dg39@ zaBf-Znm2-SL>r!E%y;OYudCIKUxv(KU$Kc?hqw?$RSv{qSD@Ut z#dfVwR0{G64JHJqV2Z_Wh^)%OqC)HKF2t}~pIQ+G_CoipHO1VeqkX}yQ>UI`ahc2H ziiiBKb^nBt(g=Q;jLBRv&UD5z4lT<|I(<_q8biPtKwg(ee*|HS-%>_|B83ch79lI~ zy9CP^=|V z8^Sf+6?aoHc8sHlO@fE|-n?q9_5mMpXXzXQuvQWM+Cj|@&1pgJ;HO`}T~3}v3!sgR zb`K1UkT~HKd8e@wu7b`+h_)dd- zqGxq|F$~pKgCTa6*80TH^|3t6a;jLhDNb9u#xE-w*a<+N2WN@~V(6(k-wQLXfinZ7<^?)JcTaFH5*Usrm2w>MLl zVm*s1I<;AUI!(B)z|Y75WCJ4##(VnHBn*yc3j^7@DK`iDY?A*K%x4egN+!Byq2X@C-$?hi7m{?Zqw8fk-i zd~^(f=@{+J-NYvC4rU~g6n_~ES*H^K&$Q}rBrHL1 zkwi&Amu+$8*BIdwN;bIi8W2hPaa&v!JPdvH5zdw1O4xk~` z9mbd2f>CesWr)|ASyRXjLjrLh{!d#@2J$a)-0DyH5SGzHd6Q(d#Obv)A&X5c+Clt4 zy-MMW&;)bCvBWcB^uvv+C@3V^2X~s|glvbWb~e#c#Y%EN@J=W{bSk3Nak02%1vu~S zu%5wTM<=Ht%8#9~5V&BJ--@ksgd)6{rs+VzQ)EBr=o|!3rYnSLm_rV+x1|^;JQ6tE6p^9g0(*R?ki7&hOk?%6^&`RlHwEr zdqX1~N96Dagd#4pRAh0mOa=@RA)M!o;4xMC1&a4~OArv>LUSKTlL#jRU8DUYH0EtY z@_q#8_ySJwLSHRmw|e#FFCsWrGs9=A0tJamY4%aW7NI!CLYb->0YXub6=vb(4P*mJ zYamDF0(bi~w>O|c&C-;9M}_HjuB`hDLzTLadKs3<#;v|tNBwa!vuw|)$`#OEy@iB^ zYrBwj^;v?^s%toGpKbWO#0jg#LZd~6Ioo77l*23sU)Ww9seikeWFgL<>|_V3J7^T< zeVJADl7Y2iVWsv-cOopC$20`Ol^?JetdTFy$?4o4%99yzpMkq49EBrXKau^sXb%x? zvT$2wk>Co;vD6ZIPHV@rFl(E?yY+A-3_72=Ik}o2GI=(|Nm5>!A4;Lmk&>u!FLc%= z=vDd0VFv``(?$y6ThP~7b%e2^E)#LKJrFi*oaGt3mRyHHRds5HmGdQ(Q{RCE4R=|PU?(FI;x;KhapimUKeRPzv^_ZKNM?y?I zJ3EoW5LA-YOar>o-d{`87kI84FlRnL3Lp<8)g4TZL5DLD6AJ<=c`guB!-aqP#(gN( zJNJ%_&smz8+sE7XHx;Y4E713W~OSsf$LCeqmvN0t(xRah3<+J9RQ0 zl?ruCA`Na#$;{W``!kMl(H(r+Z|m(sZ$AwZskK@q`2_?f?j@3FM@AFMA}*^94rrIE z_3}VLL5EZ4%t-*$c4vto=ygy=a%4VM2mJ13A zizQGd>H&BpJ&8QeXbhPF54|rWHV^23=iU74(8eqb0Uju8$Q8OB@DN z&;hMWW!Li>R%2OSG@qXh3=H}=Hgr5Q-ojG8zw6&F#1e_Xsx^LV(QJ2?bG>y|>wGZ_ z-|cTKSS%#$E?4Uc31qrGe@&#;tXBnObqH|#B~}_McI#wBLX(n`+Mn00gCWp}WpMEz z;oJZ^PEP$(EK%)qPTBQlYy6ECYqWfPe5^@H?(Am%oUir}5(_i#N;$bY?|krZ;9}4B zQR2=&nG|FO!b{;1Jl~EWj^;apg8hcEJY0*Gz*+v7q=v7SfCGhryaNXS+oWhb-N);M z?ZZQ25^>}tfQssHNF3Q-LQ>mEVMLkH`oN?w6K2HQ5 z#6`gV^-&SgE@h*G`#!~^(KYDRNQQ>6=XDAKoUq4r%(r#_bWp2KT86LV@f%)QA$Dxr zLp_kFD3prP7+r6Kbe+>exd!^g;53zF{qa9}!NsoXNr`5p?%7Qq#l@f7xDru@d z&sUhAzrB4quZ{ComZ(IlE%D`g%svj3o|9A479f|n?_lrX5EsRR31E~l%h+`p<%L4- zc>UCd#bN)p)c=QQ8F3hpC8okB$AEDDz}j4mbsEqD8Qfc=T$0hVtIp8B;Srtt#Zf;mRJXYE~j~0W9yUdqN zdTM?9cvv~7j?-9~u@cqU7 z1Vrg{PLi>AKm(uG8wn!e8yV{ENSuZu{?F%&0M(W)m7SQ)lLM-j>NC1;X@M4%mz0Mu znq&a^(BSsx^JmdgRuOPmY(g3hE;D6Kz%DQi0ZHsUDl5oWnyQVe`tLXY=XGyQ2iP<~ zJ!@-gyNJ^T*fW$`>`t+yQkY}SHaj(?J-igM_+9a|XI9FF(@%gdSQfUA0A0hx?|EJH zRP-i<{g!Lm{nufO_j@de=;-8JZk>c65Vu2LqTff~mpI@q%R@;%%D7%Y(Lni6yH!y_ zk~5#GC@R9Yqbed->o1fAe?pNHl;sQc4aJvFASLfukma``$me%$+4yGLD+5Vfk9<9G zL;Rj)2cTAYO!W*sI(}_@_dbx~8{qD68x?@N=cQZv>IAl?*E0#bLb#h+CNa5~o}NCA zz*S^%-Bh=Xc3;Br*$}HHzdDigPoXY}NVlaxf6wXVH1C!5Sk{4#t)IBKIHE&&!<#p% z5cROU!p!b;jzBQuCBI((hma^4UX7skWl&eC*&2h`fC>yIsUAckXm(T_56$iE?QoME z3z`}JFifrNooYc)oTNyZrjU9;0KZsJ)7F)vEXvNqtRhe@uPbiF@{CleRFPXnA@es$ z@PEZ_K8OU?U)ZDwA4DWdkm1GsA+7$x$o;Pgt-*mLc*zqeJ{pH7{yBISj|arFhyzn; ze*$ew_`E-Uc@7TvSvVwRLr2H;L{4On`iV(zF3RJw5GTU$0a> zP$~!R?a8x=jgKYu4f*l!u=}?ar1k!Qy1%BCK~i740jBA`m4sa84X^n<2>}xAt=yU9 z57+`77}G~~Rk<9PU)ovdN~X3s=i2kXcK+{HEkMvk7@#@}vE}~Lrx?GgupecY{`3u+ zrT{>sX(|(c^Cyfx#tT5&M&6^ee+t*ci2#64+$}f#Z01!h9qUiXi%C8D0 z0N1H3Bkj5WVVyR=0MZTC7}>QFe|XI`|M)eRADQF_e~M?4e7tvz4&$_bg#f%hpa9E} zP*JsY+o@dhad2?dG&Qxw77e%j72d~#0ERi*Be0Jwmdyw+DkhcWCwCQ_nka`Es8DWT zZC{-v?z6sqcBEeZus8ABU^nFf;Le)Dw3ZG4qUsP?u`|fY;(mUMGdBGJ+|ET!*u(HuT|Hej5-z7-w-umKKQ{P~AyQ`) zr0#0`Z57#pqRmi81s%wt*}{v~-gP-BFE8)0WtzMEq@E7*H;;=q1};$MRdxEz=NhF! z2caJaxnZ&x+m`!>Q+m z5IEb=IY~D~wgbYgfeu%7iZ@%8u%?*<`%IK0vPw?!9KQ2w!=UQ0H2f;sJJ4A+Jmy=F zrFv7p{Gx(wk9st(_YbBkoqqOhue3IHb}YT!Eu^^~`T6<7*=SEAy)Jh7`~#!ceNhSp z@_Up4+G$-nC^XyK`nzWfI`QTlP6_7^T;w%1HHdaE$)HNb#FLY3@1I7!aNd8TU-(c( z@Rp#!Lj!Zc=v^o%rXl4MP6&@?fZlAoFVJYWo(iH?sY$a+JekZ>9Zt_rHV zwZtMkGuzJ;q0$meZ@CJ7sXY(wCn=C{_HN_!lu|0u8X3E-f!unAfuEbsGcv+x+Kv{) z<|1&QbRC_+>C4Z(D1kl>g2$kL%JQg1h=G%ab+nc6gSPcLi6&&*U=I|df*U@q_-^}! zL~B|3VJN*cw5Ti&!>`hQQq@b*Q_D+mJ8q^xI6DZ#Kq_3o;_l7)(dH58Xogvlw49yX zEjKRTE`!%)H`T1x)~VdR?be$(S9Q(70i;0Xx%8{#5R!t?@M=b~N^m(3I$d28Hj*(U z<64?ZqNNmNiszjQpYMcZKDMN`n_#RwL%v*flFc?qyXJKz+d>lPXWRhAcq6x+{)rJ> z?6NSJ^E6YtnEe2mp_`u;eR5*m?&eCD(PWjCq;ppoVe#=!U(cfR(7pIw3pua**j=)f zs_iS$3|&=y9wWhEq)ef1`)@3f2+z#H7H>Vzt6Vp9=J67DXC+M$xBXg_ka%tlQY_h6 zfnKM8s;IF~W&31Y8(~nKk`&(U8}Zl(h!Rf|Je*-Yoeyx$aHw&vIH{kpFYxMR$Z+v=ICBrO1C@}E)@qZ;v~$c6FHTL|V%f0Db-uV& z-S%L6s@aJ)ZeIW`1dPo!0LQ$=QRbmtFkB=RmF2AldTBUqdNwj!NyJNPdMw^*;k|=r zHMf>3PM)nat_^tVblA3>F4icuu1`-0!hTz6RQo!SOnUQ{PJ-vb&d=G=UX5dz81lhd z(B4-V#E@VapNOard%+~YLW6l^Rc)r=Ax{P%o9rsVs~gRBu)7-`4_-gymgG|m8X+$` zvtxF2#Ad2Msd-p4K29wrA(bF>b0BUq_xbAbGF}MgK&*?*b!0I_p+IPCJ~lqyuyPM@ zRQ@Fi)f@}OdVBV;s^Q6fzJ0nYTe{x@QUZyjf*adu?MeC6oT?77)}?0T?-#WBQtbCK zy?FP7aZy9P8({_Pz~Uto#M%?la3Yhn1a*^ijL<-G(anQZj*``|cgxjdnfZvrCeOKt z586-Q0U~|Ah6;A{%sO%Q@fl>h7pqHT_$7G9dijeW+Nj8TosM!Z&>LL3g|Ud+68QJ* zouc>A4Gx2R8x*<_zr1^(-s>C~Q8KKd8vJZZT+WBFuNeMrpj)1AKOgnCESDgSuV7h& zTW#47n<}nvqp${s*5vr<2RF7pM}mZLY>_{;asWhv9$H4y_Aa#04QvC6ds|*UdVK3? z?$l`2;ru)pVf|60&!%ekjIg5gdF1;~MVF5rlR><>+kC7~N`rWPEqC^g(f2KcBqH}Y*zuhPk*LtdJfu~cPALHQyg2?5>f#H>@EYj{UffZ<^Mm5BezHv}meVw26i z>08%$pr`}cc^NPt>EQit6xJ>iXBo8ikkie^nhoLy0=@M{la%`VYHC00>JJww5a&#$6M88% zY~rnbdJn$I*$8L?fcH!Qd|mn#;&~`Hg$~^k8G`fRj`bwoDcaWhh5&Ud<`fIHQ>V@j zm$S(@yO`f%?vy8`>YMjHCHUcU2W)s|X1&<{MQ|d>P#l3e+i1imfMV4{W0mqKa(z`u zXC!g{a5P)nreT(AQI~qPhU@l*rmN*fmmVz7W=DI64On|pksunw5wIst6o+Xt@B^FF z`zOh?FKRC!zyL8P`n+X!?$OvxOlUei-vC096Tx6(NwkS6Gex8KXDfiKLOeWP!>cc+ zw!vzhE-YuH2mE%1@{G<;dHTmzR%T_B8EvTheH#JSo+*)rKTFS7X_1`6Bb^dxgF*yl z+DW_0AE0cW5WpU${VgO3;dK=*>?b82Y2oUY|4hWf+H}*18;5Hxh}VC7eff+Tf`r`y z3JH16dJsJK&N()WXjOjxw%*hL+T#)`CK-nnM3flrkF1W)z~<}=Mb7Iy=KmB zvN|qDiRwSFCrj0NSW^wETRE%k9Ikxw9GN*1qs`Js-WCE!g5FJaQ`ex3H8XZ^KRTOR zhyJM9encI7*so}ivK({_2Abee)$flin(h+j6m?D1)amkdw{`N+B>G`Du8#3O#hr4k zh}D9`AH+Vl;MJ+EedNev zv556~qiM6h6BPDhJ+aSxEC;mZ>!G}h)ui-eyIbGP^1-_SXMbRe^{|&~eZVSACcCtQ z^)r98Nz4i>n89?yrju0_Rn>a=4a(BTn(VnC3B$=jau-kppscl(>0qWDKGmL!HR=oI zA^42!veztK>{j2b*hGp??@gkezSch|SojI5Y&z*Ef!i6}irNF+I_GGDZomZ{?SI;r zxc?+GV_a3z`D(#VxuM(eX1Cgf*IWkjlS=vic~eWZ9i*2p5BiJsD5^~md9^jK8__+n z!N&)$Hev1bR;fxWRlZ($fRy1kt6Z>7kL;Ry|GF-)8}r92p4vUtSn5HCPI&2ke$GR1 z2v0Ub%Jcrv&QKJxxG(uU>m%GMN&`2NNz&*m$bx<^H=1sy@^dPR!KR$fH7}4?Tk7LZ zETB0__>P=`#ojXKrn+w#ryQBYDqGqYgrPetnNky7Kt`)z7gxp)jL2v&Sn_VLgp;W4 z#&%B)F^<8T(}#gRtoJzUC1eO5aj}U6bUPa{!Y1ziPLl{bD=>0-uS4&ny4ElIJ*1D2 zIxkt6A$blk*x0q!JY+}T>)`x-O8_G1-&}4(j0e`%1h^j$EifKlS%PVc@hL{c5YZL+ zgd3Bl5#RBP^46mm~e&#m703LXrpq5uogCe4|(HxC6Eb+!~Fu z@QZNHem#VK8rx(5+a9@Iqc?_xyumkPC0N_6Bt_Zdbq`gGm{&SAM;NT4fI2qJEKBTB zyj^Kc?6M!KV+f?b4YYfHdl5avV~3)r+RWN%DIFl>-%`~NEnj%l@BRMhI6K_YUq7b< zvZYyOnlHLr6Gs}rn<(lVDe=&xi*!q6Lp~WSecl!}c1)o5DjDq21fe1eJ$}-HJ0&x|I|)0n48_2=5UuVUVw047QWm0J63# zN?QcR5t)XZ>GoMz>XG+D07AcIIUeyCf=f1kL?^_u$i=L<6R=eBup@JtV+AVb1dswSOl8o{?qxr>B0w^v@`Z##%O{w-Ew$0Yoftm zBelC1k6%zXHas7Qj@I+g1pP9ubssE?vo1S7zUs$^Fgq7oS-Bj%$`tf9XzdQ?_=)md z#x88&5SYu5coR%SYVA*Jflvd8%Vc5>@TeqiZ`^}g4& z8edboR`rT|v|J4CSG%Heu4M8f>+0DEseUuAdT4{E1J#-b$Cf<49$5eFXZP45oRj+_ni~V$HDpy<^twSm17H zV+Z(CFLuGC<^-aaC&6CHOR(!K6!duc&$Ze@XrNDXEKY*H7-Ru><(pc`PnFbFCjXv%hfrlpl0jKVOQ1S3S`1KhPk>QiPOS-bM@ZC}h}AJQ)S6bpSHZ;m>L&-1bSAq2 z=5@u^UPH{zD_d_9RyWjr&y{%)jmWaMD?FIS8^guqswFgQr@Meu9WYkl^gD$p%1^xB z*D!q+{CW?qGjk)9-Z`}MaC*IKi7SuCF%=>vqQ3ANAZSup5n^A|o*CCqI6OLE<*~j! zPw`Z~c&#+fUAS_i-eG=|Qn|Pr>U$hl;p?0AYu7T8zbF(YpFWuz`q2tXSzL^6&8$!~ zk0fn|{WNK10}t_H=W$*EZ+rf38Nmp5l}LBd=T2|s1j(vGq{)K=TUN33M9;Ie^ukoZq$bDc_REVy+f?>z^;6ja zAIf5u&7&id5U3FN52r>;U11Tbg$kvJ*!rI1%+W0uQH3bQ!Opr<+Af{%NVP=zWr}cB z6na}ss3Y;29S%~;2)nPbWE~TKA(r5Z}FJ`aaVlOT{7G)cOzo#pbb5*;IZe=HHh7P zAk&cG@GmX15V-0k5*3hzC@W@03pit(efAn&X_8dwgHf-jpVY|uu1fL-Xe@O&@H2;Z z$>x#T=Ixwn#`T6v#cJtiCs|vS^R59Z5LO?Iq|lckt+bEDa&!z%S*$ z`uG+(_YUBcu=Jx}YbcyT!Y6q1yIvNi5`k{iX3=|UrBtek2J;rB0)2rEfO5*76r_O! zF-!eo`{4*&T~W@0VUk4G!ELc4B46$teV!lyA9F}LjSe@e1VOJ7F@(QgkT~>%XSi{; zDT3K?v(<+_*GJ_ac64lK0$@Qh2SS938*lY3Qv5Z2nDEKBYK^cDNi2mH0?=aZjAI_C ztuwtE41~K~DoP1~O6^k4?}DfPdTnt7jGyOyVYjj)aX15ct*Wz5nFyv zkmOWQPp~3l6L1aL!R|>!20TeN2Wz=j2QU*NshCJULI1#Dc480qjC59$F!kYlxGNpr ztneai4gnESN4M8PqILFzQ!g#NdfDHyr7G+`KLB+=m-58IZoTioE*p6tG>wrj+;)TR zzMPfJ54Oz418Ws1G2p3^;6NBpuFUaS3LCks+3<#K-gVJxQtCFeFe?{=AjDQ4L3nI^ zAjNms>T_w^1x@D~Yxwb2t{9M>Y3I4Jej<#VjrBOi{RrLi;;@B2B{hF#`^p1LDG3V5xOg`faIl($Ph7NXIs@uOR)jYU7Pb!-3PM)2S`R@U-F}kh^MM0VofDD^eQ+6bU4{P6F3 z<11`s>cm}uqiGbvWcY@@^sKvTK9zW_sPio>;+}kvJ3&QuJdMaeT<3cd0)eFP%9-~= zWjlrVen^?VfIHFlZKkGEg)Td@ze1-X+Kpai?a>e}*FoFQIrk2;Ub&yC`MwYwWmoU) zIngj5*(G;hxc*~GgF@yRqNlco@$%|Zxek*Q{ukRI_qzdE1#Fvz$+PQ&bPV}2X)c%Y z3kFdu@i-034h7E?PVIGPqwL$$*XGkz1cEHj+sswR1Fq|yX*#Km#Js9Ic6$|w1_yIx z%ox2NQ2>cCs0jUP27W=mz=*vxgsicZsxERM6Ap*b{C4i!uz9`dw_4dl#?Sa8?9^6<zC~Oh1 zh8&lhsCK4WmZ%^gz%(L2J#_$M3!Z3CIaE+Q!#7-B-$KrQsTWA~N#b0imwa1w9RDWK zj63(@Ao2n0ec`quXT5dwZH8!KcW;V2v1 zwGnzy*#?qt3MX&gc>2;Uqs}N3Ug|nZ1LpPU-aoOV?vYeE8BJtxluLAP8X1U&@F$d` zUv=*y2$xnrTx3lus($)(QOc6?b$}_tS9bNqYlmP6z_}y=i=VoKpG$H*rO2ni8x_?0!@f zPVGL{Sh@D?f;97yhsC+X)rw7YMV%6uP82jeOXKR6OOZx2ZYD|b5+hBMC82<_hUiY=#NG^@@EC;OM!e4yC%6c=LaTj;*b_I` zH0vEEMs}_bpw>y@Su6+P5kicL0Q=f7;E~#tIkbs9^0Q8z#j&KG0 zkAv?;hA)e9&XLmlT?mwvCdY!Vn8)8{t%$jvIcdA3Q+3xf3MVv@ZH{k>{#tX3?oJT@ zOLd)N&m=K$`n;Yq>F;^uxkWB3eDL7)cn;?cCYs=81o?VGav2U|atc|zeNA(ZwKQf$ zZC?(RgL|wQ9n8gWR-jlbS=?JR?+M%A7%$*Fn%R~|Uf#~~Y16-=px0aHu%ELFYxM9B znMcT(vw-g75$&g%Ewb*e@tdpQ-RPWoZ-_ZC-(k9IXUBV z=4dBdz&^DwCUBL(_m<^lxo{-~P{wyHm6thge&QfuabApSi%?BJ$)g!u4JiF*0j3HS z_<(p&58UGqC(WO!iBMJ1jvM3D>%U`hD4H~PxR9a;k>HzhWQ^I&a>^2jJj>5?!VZw6 zIUZk9fvolD(uNvwhYv%ge?GhR%<*W$d|N!OODM6+9=c;Q`7X?}y1=5sIbt3~=c==2 zDv;nC8l1>#aWcIc*jgq+?RYH$c^^hY3&T(Scs>H)(0R#5P~@@cqO=!;3*OeN+y%eyF6t>gba%Rg?GlT zKH|-UCv`HLFz2mU2rukQ7LX5LZ1@`XZ5N$7@^>H#Bn%9U;)-Z|+)P|iDQjkr6SfX! z5)zDya0Tf#P|ds{eS^p}8JQ{u1@CdGZ=R<40%z6uFKrxmk`~f8RKHg`)q%<4W{7RQ z>H&ZxXEv6~Q1>`ns8>y8h`Fm|2RU-p%Up=%{c-HE2^w;OTa{WT*Vz!RJX!~O#ufHh zwamcKycN0(<#8^Dj$%d;p+d!aa1q{gGjcn44&g55T~jQgp?&qi9UxJfIkBYt&``mC zMToUkm9M3396~m&J0A!5)s`NF{Tz$FbZz* zs4o%gp5&u3gIJhjCFriag?mmb2Y`%8dyKK=o>^3}tvH>yR)q2X#xe1^<_9o{*<5}o z$r4{)0G$zcm)RGT#v{F?lm51R0H$!f%dfKe3la4)D0Ym74vG-5DY2|P;5J3}*x1sx z5d5|5d~iA7hq+|D2v=(dTfwLPqXr>yUDo#BDF~XM0XzV-@JKX3!9E?%XOwSU6plAX z7zJVSp!dtV@&FZ8Di~bTj%x)MXMJ+ZXLo2;$x8pZz!@tPDo1sLy z#>Qlkume~~vH)_+mH||3d^#n&-0!r3|K*K$KO#*BxHz$(K~?O!y`ky!qYO~u`zzk^ zfdUIlylxWWV$l7w6`p(nbWT zGE(JFtkM<&08wg{(@Z-4H`d0#){-_IFd!J?A*#Q6;Quaj_A0=2Wi4@;$o|CNLi^R; z5>U63@Kc0axAd_0`yXG7Da-*+Sx7beEAU=Dd{ZfF)eSB)sq5Fa)2SN^Zc^l^xF3+lq-dH z>ZMsOwX;t4{HIw!x};drkvZHLUW1l!MkXC&vk+EO~w0EZ5t=RMN=b}j5!Ce_uINt=(k}mJ=kGBIeC5=teXe!-$q-B z5HWXYK{$f4ri>20wb^+2)_;76(|ceUH8+lW7@~Fh_Rs*Gh=m0jQq^X+{@B~OxFclW z?{=a(?DlhPndk@pqQ;Y!Mn^TR%RhR)#KC?si;xA4p?k-?v>argbW{Y6m9OJj-WF+{ zSjvcT%pmp|gKkix5;mNSEt3k`^tXPpjS!s)6xelSx1WEht2G);6}V=={=w>B9R$%j z7Kc+NwgZnhvtLy)v@7$%Ia?d*-gN3MD3}q?j>2ign{N!%jvG9WaE3InrvkFb!^e=VT5{>jDksEIq-3WA)_p;N+dEb)rbC=(@V`u5i$0& zFzcj@6J#7X!Fu5d3SOJhD0#JS1b*(TB!j!Ux?-;c5z#&84Ml7S*y=ESB zarr9n;ao6xD#P3c(nS;aW(ap>`9u4C=Q{F~C9)g|Yz6v43xk9ZzF%|wpoJYO5ce*s zVB+8E1~i{r9{RAb92AC!-bYs>iIB0>}KBJp|`0E^(4-9;&^-tAaP*+dNwMNvl|O zQ;QAWw&~Ieo`;nN>8(@;6i|DC&e*@Te49EW1D4(~_7WQP*r-6MsN1~QC+|Z$i*d8D za@e1<-)lV!lGue<<-vZiv8zoQijjC~+* zTak4I(YXydx6qTGWFOkD{|H%b6;v6l*i?bMBbbq~De@(fI)PO6-gM4^$0>@ zW!A!--FezE<1ige-HmNb@G*+=4k7Fw5p&bBc`R$&5z`8FYP73F?$2)phqL=Q8Kp>S zy2zID@rD43@hdi6Q3t2-N}^h_Gu#Y6YIgzH*Knr?wB+Mgq@E6}%n7~QgIyCcpyViM z^2(puKo5=s?Y9js9>>ZqhA-e^lPGe+G)q)Yo*h4e`IcFJXCne$UgEL5B~iTAc8<{) zSdrp@0=f7Gtlsq%*Ii7ZE?rG_A=PqGK&Y`H2APxk#=Po;(-*?Ni9VHKs$j)t1giN- z>sij}jp(-HF$tErFRKvt;<3Vh?}-2^V~kINSt_%s^(JmdVJYWIL7?eO%Q^Rv-*eSE z9$m&UsD1+HuL~fEr}CnnR7q4|?w5#pdgZWZ8w!8+s&S{8#te-F=c8L&B!6sa^D%XF z+Yzs|cB?OTj*et=Zebq=Fb44HR%K_>y}--vBj^WHu%g6y%0@ipwcd>Ok>0DT`y4swZ-;2?8UT1VowP@NOsGVCbZxaS?S zz)IAZm50X2*#u>pfdE!sN<1@jya*>tJl_@{l);VB|hzUQJLk6@DGV8c+c6Ygn zctFCA)Z%S-tH3ceu(=(V6*919>%1x|5D-%mNbSR!kWKWtPt>pwV0JJ#q#>}}dN)c+ zNF}uaczR7eQWYGzoQk|ZVeFhC<%sZBwRFDXk7jfGSL;0-jEjKP#BAQlrom&Z)rGX2 z^Bfwm6B_-X{~Mk37fN9Ck*C3DCCJ?rLB4%Pr`nIw)KcOORPdO@gt)AGa)4&o_VK;5 zwe!8frhWIhX~g&r31CBxakRiTRRT_!YxeR$^FwD%OM4)9v#b8ns|+byZ0)lmgNYAU z_qH*4ssSo(rOMuq-ywa@R1>(d<9r!?4R;Pn2*!Da8W^Rfe(r@-0Q*fZ82WcKmF|<_ zz_V{O=z-lBFnL}~KJA|ngwsRkJRc968wzMz>Tx6Oc^3ygZzikHlP^k85@O~$R2rNB z+Z2HD@4v5qDgi??iGa+|$YSmR-1>QfUH$Zi4H}XKzgD9!GmZh=3ml9|+dS2fYz4}H z?=zCmRbRF5#~AF~H(^^@VFR!`>{YJCnK$PZ?-bWGv*3ClktN%Jdh?~9CV|j_8}f}- zz#80VlQ@0-yCT|pVXMVu1$gDzNFp3vGXDrJ^6V11k7PeW8$o;>i|i=J>nnWIlF4un9X#aRZCK^i6x(HIHXPwg&qZEA$CiVhC9Phv(; zEH%LFn+3RZiunZHMh8Wz{>@vdh*v}}iqG`0&4|5zCYRotUkF5FhAj{x6W2% z|IWOtGMkSc$QW4xD1XuGppeL4jo(CV9QQqGHgHIv;UDJm&v^t6Y$W6@$-#+HuNp{s1Db%&WDo-Q27O<_y zgIXj=uqLL7n<@Xklbv%D+g-O@OI|zVBCM4<3TS%QU>5ABdx50=ir^J(bC0HUt&WGYOS)OZIHkcT~==q@vPr`rt)`4_i@M zg1WSfUPLu~*1$f#JC)GOs-n;+VuGI_EG(T8EtDp?Dp!i*+KZ_5&H-(0>KC2^x~_TNdBgaj<`aX z4iTv0Fk3WMfc{-o14%4?zUp?QLwmN2k#R~&)EQ8^2~3afQMUP|cK_H$4E-1Am`_e# zb?R>}ogHFM)s=f>f5&$5pgsx`izGG=cB>2;N!2yEPvbj^|AVb8;SeeO1cMFdI2uuJc{f*8Ro0RL-z2mi(m>a(3*g3ySw{Y>b;*i>>v zN4KUbk*cTZV;!g>IEXS5G8^wtK(3A+68)Sw_i6~p5c0G&`Czl!{>X7LKPi(5d=t#ASPIfqkljaF(4L)&LV=@jekDR_LN#z zl?5ZWfd7TPME2_f!JXdTBswyij(9wZ1(*!UpeGZ z-sMS^JhC5OX3r++*Cup%s(;zb+cpW)t%)wN{l&z#vo^tgC)e{Xhgeme#`X! z>cbF2Vzpap$rp)t^NKrft-i#1OMgYN zh5Ca)uMG?(TE1ZtRzOB{!m~?k z5=HieALav(^md%@lE<)g%lLz(cAxaLXyeP`%mq`#wY8XD;G9qFclG3id@VIX7Ex4diPkilqq!rnQAKkKWBjm)5jJ;x8-97d8!yI?3`I|?!CZ9|~!{_YV3f#R| z1zxu(n0@yh-Ag5Q_&zo#2lG^~=8?-U%!;?K@aE{#G)C#cqSFfa=mXUio0~__Qusyy z8xdS^z?`XJE)VYB=&Noe(F>-a=)R^!y#-zUHI2n4ZKSJYe4ptrHP2;TK?}5Qv8ufL zeqjj=036kZB30sEbZ8#yRS|FG`Z4i}5qDpC{5FaujlMEYGLh%7xE5_yG`V2xcZ|y7 zYkF>%1q(a(XfH?IF-W6S)9&Uf@0@s9e@7TN4So(Bb`k?_*l8B@8-?ny`-n6oy!`fo zUR7odyvxMHR{ndNi8Y)4sWojuMp*tpn{V_6Bv+oVUAV^=U@KO2bzs&d=*hn8wI5Gt z>IZ=+dW%%WQ{UuE2=ROwHI``NHuaBp?0@U#;w%q z+Xl_;1;g_$!&^TXyJ=cx)hh9Pjy3coB=F3TB-{o+(62Mu#`^#~lqzh4$`B?t0e;INVS)4O z+5S`^u7gd~;r?44g$xj;B)h8rjEPZMw9vlQHN?a7(3@_A3JUR)x#oZqWY>y6x`Gtgg3XiUZ*-p zjvc#34j*Djj^1p^&w8gl&w-upbpZGqaf8$`-d2LKG^>GNqf$97&vIX3sr`sSun6lH=d z<0|IM@&L=IcpG|B8U7%`!Ph?t;(miGvSr{A^*~%%mTot$;!tMJWVKrP__4z z4&gRM$ZOzRJtfI;tJBOVLSOtGPY_(6FOx29F;P>Hwdf2wzn%IQMamd~f`If8=}BeZ zHp_%XP+i=psHaFGwK+&$08YF#{Hj8EVnVy4Y%8B6lib$l$}Se@>+*1O@LBPYv}9;EL6g_ZuTOp#Ulx)Lf#g?S_2InUTix4BCfllUlqgf%CN(3>2EN} z7yY02wvFvnM-7V-0Wefs={Tq<(?fCq`OoA3dKF$Y&bCP%_Y|((KLNmGiXTCEbwpGI z(x0&7M-cB&(I=D7RQ?YVJz^j&>+FFdK-}-3E$9&j89`X~HV~v7@h4#U|Ewz4G`A9y zvW!5)g^$L@1_2oW=-(UU=latC;^xK$B%h>M3KpRz$cd9qB}HwXoTXPK|C!?-p|8K_ z)wH34^^GmuGLq!0@6(k*K?&|AD;Oe-L&%+fazFp$ljXb+F65OA2Bi=I{MDb6w-SKG zcH)1sY%7$eXqB7$rOlr%Qh?-lsN!Rz1OGu9{Ofr(K$1$?|L@NK{}uwpca1crrS9zd zT*9R#sZozk_Q;yNZyml?9{F)~VEOv8k_yN4VEs>|V)eOA!lX>Vq3>MAeXY>=VS6_L zmeDwAcX{r4)6KxdCP~EJ%U~3jfbqXUzrT-ENl?;dewEv1IjyYDsd(Ws^k3A^aQKq&4(LIxzXX8>Z`7ZD0J*br0 zH5+x<3HiZ8b@FtcA|@MDk{h|VLBT^L#M{p)8RQ0x3-mShVloaEA+_IjC>foQl89_I zrnoUbq*Y=VJxh{H%8tezfS*iuI40M^4ZYp$_*C`IVS-@a(^>6pu!|>J%xaT-7Vm0Z z0Uf|awq3uPa4vu78pLdUabDjDU=|~;+syx_ldrH?T?`k*cX@tzU|;c3rWp5389fOP8UuFt&r!p)U-Ff;k1HCd#@Sf zQQfPpv@JR|?P949W2H&lrs15Dz|*_)&D99Q2@eJ^d(8@4YDs^^O zhk;^fXglckS3ywjT$^o0)B@%FWFrA>c_7Me!Pf!_FK$NVSpr#AB-s;lbuEIX<^`>{ zI^LK6p2M;Dt7IrqkJp zraUkjVl~NEA8QnF)Nel1*fl;pH!7(g$Dr@(7V5qg;P(mSWw!~uZRV2R)dt^rL^3Ex zEep&Jh~kHo@bo;lcmC41*N?I^-JVQH&EZ?68$+2l2;y3kVG8$nuaM-3gFn}S9vZNVD5-Y zjkYXa`i1&12D?wqdwKYAMT8IKU(x+9#t`}Z5JDPbzMTlCn|ALF8rwr!T_u)VYauz( z9BcKsLRqwNjZQlFCZu`Sz$m)SPi|%q+&}CfH{;K(-90gVjfAOmJl_+`8&?bJjY-))m%mAd_+1ycV5`%N#TAHa;#hcCxx}4Wt zXz5vR*4F9J)$uro?*7r>6;of%O%F0auw zL5Q$>8{cv`1SX1K)E0&bOeAFZ^fIzSoJ(l+J|_KmP7@B3rha&DPTR&zbAmNBXb;8t zg5F3HI{r+M<8635!J+0z?S3FtfcWVXz3=-Y1;0*O!+To13sg~xSACjvQ&j#Mwx!gz zo9T9b-?P+DkD)K<5iB!oF}f^Kl~}dc7%RVJ;Od=I#I%LF?sm?qY5^b=@vUX;KOODM=sE$kbe^I5=P^x_Twlv#;ASlFvk#w5}iBBk=;ea67GD2rr4 zG7>E055Zo?E1c-&aeilRY|bFMtlrj627czOGN@fo2i%k{x?~SCCItM>9GHlI-`B zm~QyF-0S<;n!?JX*dqa)J#jqN$}H1&6+uo>6iqQ@v)E?Jy)tizmxXpq>d40Pugvk& zND1~v?2f)#b~7agR=+k!x`Pf9LuH$mvJLW4{}8NC`kFPZL#(o)R3Mp1>+AgcSW_qV z2gm@4#RYKp4gdNUTx#@nz!MSvTyawi=U{5XYyw@_J08c~7K9J8)8SBgwweWw^(5e> zA6~?00vEu~1pJmUZq~jYut|jtQWbwIdL6pk=w#c<>#jBfiUd^nB0x+gMFM{qNCM3w zcloeB{RQZorAiRFcm+s?MJ?T_1~P5>akWK<1@00xvm#xylZ4gh|F&!aB{Na{q-F8A zweG`75rR;gwXcyJqTqb$$!cevgU!*wpwu=~ZuYsmj368Yk*d$F=<AdZIPL1W zVP=!B4vL>oW;1tK28P5Q6?uqoKB3(ma~`0WVMxR>A4UPdSh+Hj9U;RIxOZ#HN)LIF z$qS?qImhdF!t5VfxW1NOHNL}-4f8j;bop_TjA5^IBOeCQ$%o*%B(pZ&5nRSIM<|KG zQ+WoagQ+DSDP7g(4!(Ll-?xqv9aI#1k6ix-jQ!rqb&V@e=!>FK?k73?SJsDtP6(K# zS8Ym`(O(kRmvIMBr==S2O159GozOK9b!|oKSV+x$&zXs4!ax6)0MM6rKOrxsv^7zm zmA3YySh>W8LJ3K}((2XaoIx%$3bJicc+Kb>r3NB&h#>ACEmgu#tM-T6+CjO#x0x`? zh}j1OVajIL69S*h3Eaxl?@jQzo>VlFD9gC%2$$6qxIYB*LD9ZwQ?9c`RZ$gWAHTFZ zMO%rlJa)jj&T&HnqqOM{^^b|psg7>0J&D&Py7mVfX3;x3`4?5fDl1yrBE~R#^%%s(ukK@tNBgm{^d;7?f~V^yS&#Q2T0N9 zA|Yd9_UC)xaMUXAeJKx6K)^lLj4DXn?>q7(SCAQs4o|qJnO?9o!KnDOqS1Sl6)_!^ zPT@tZjTj$(duwNRIBMcUwDcS{OnyJ?JZ}Z>YG?i8Kx@Lse=T#3{RP)$cF0)~omtTs z898Pvh+bOC%ANdjS8zvPC@?U70{8H6{S9@@1KlWs7{YuQ8;h9fbd;4e&ot@}JY7N+ z5dFUW6Y&mUYL|#QZ4JkcZ@cbvX3pXZ;|P>c)*laa8M`bR=cOm>jlfsgJl5HcURR+} zgQZBkP<5RN2+{}T_J1CZL3vOlVumNau5d1i=K2t27txUImQx^=HinJZSjWX`PVb^3 zxaDWu9O(DPcDn1!T^V@XiXPGSy^_gE3k1tiC7WjcogY{?a~CV&8^_}sNEL-c?wau_Ed!ca`$gc9$N ze|kwO{#z#vI5`n5=)xy25>(b=K~f}(-1aW0FhLqw%^TbZW^p6DxsBzTP`7btG?IA5 zMn}>UqgSVUl6jnX5?eveWBHN~ZnoS9)P_FC6VGzPhwJRb)yQJp)(`b3o&OFK(n3jF zLj_1-32HXkoEOrr=PG`zxGa2;9s_aa@i3ZIO!c~#%4#9d1^p5Z-xkHK!nV*Y1z{e$ zYd$@h_kzcNI6d7bxu!Lig3H0q=)Z}5>Amr4zJJEFpd+&^Z$o&)bgb|&a!0$%eBiq1 zxg6Xc3QDiAHPq>8hBMD>yH0+D=;vJnVfv09uT^Y~$XxVpox9OC9}Dt~e@KSm&7koU z609-AXqr^KGW8}>#=HN1K0O^-0SdLNa@IGAuznbZz&*4*Uc_l|CVW!e1q_bNb?B`J z+X_zyp2$w1*P%MKbh(-y#}NRktEFs;{YOTAJ34}qEuuaA)*LYKPk#B zF~;7R)AiksbK4x1&1!cJUWwG;u}uQTf9R=9glNf4$~h0})z=n8G+0T(&T<^mDwbYm zIC^D2?9aY#3k`2lrj3d?&t^yJw%A1(og~Nxy0QE9uP#TU@F$_!Xs#rV6L2kmtfInk z`!uVThZLbAf{T#qwpO(ST@##)VTP#-G3~9?ezxji&lC_!9`8zo7IWVs#QLXZ(a*HV zN$IR+7tY(@~SI9-iH7KqdXq=~Zzx4s$6i{FaKg=;BcOipuiHNlF3mY?blzTkM?b5H_z5c!ls@8GMbeIA4#_C^IG*R{1J~etIJ?lu zK#85t0F_v52MUKa-VqG11`aUc2n%v6Q?)9Y8Umi8(FzDn>e-_F;Ih|W>IQ(+2oUNw1`gI^TtN*KuxMX9?J zTm5cR5m5+HQhO19Cyn!3hxGM(W(xe+H}$-!=1LM0RLsq|LB<~G&(=Ii`v98D4p0Uf zm5_Q(nt~{Pt=Pn%0ik8MkNmX}+oWVbWBh)OGr@%bmT^6-Ig?yTx-@VcSURhNXiNl^ zFh?}1S#TO$ph{IyGcs>6^zS}$1WKNwP-^Shj5-CVIcTDs?3UF%(|S|$^-VJm$Xa?y zFO6m07U_s*E{Ot)bYt3|>SGQI%bQ^D(r}W>&I}hvYuiLW)ZWhofn(~kiV2NyaHMoS zDg_xFJRZiD@y!y;HaEcW(@zSIb;EMdKDt{opLd-UnnQ`XQ$SZX?7eI8z*cJAuP1J#0p!|7C|TW zHFhQ=^LM;baREN<1TFDqBGLc+8>IF`f8BKSH2egL5f#pU0&cZ0HB1qNv=GVQe*t)> z@VF(_sL11?W_?l)7emTeTSZGFz($)%$(VE)|J_~Ho5}Wqj+v-`Rbwainkt_9mBvD~eNZXg#TXMQbIse1|(5Jr2Ug4@)x`>m<`IB#)t?*`W?dm4+IR- zi^u;P1pM`c>ZiV@Fj^J(A5iI^dhzXl){77R3GGvnu>V@r-{&B`c-Oy!A-|qvDT1N} z2x2`jzqi+a^dT`&Z;^kCRs8oShBPSeJv$8WYf=Au4gs1Z%9Pse<2EHQ`gp(-pGknBE#70(Xa;I%(=>STj7v}N> z=k)#^IRzB~x)T9TEW3yO4>nUA{fWu%GJ+NUn~5L@A@(!rpXv)B3XM~08XN2Vu zdhO~@fg7ryt`z)w@ei#b$OX#W&ra(AOXL58$6y45yeUWV{V!Y0|7``T-JfawmsoyN zztV;Ny6m6xlV^Y-ZS_aS#7}~P!!v~4{hw@r@iPJ?9B%ywzt2khNrV|S0F(dsUPyjW z5yBv|D^XP;{ozHvID$$YyhHrH|0jNp(obr`|M`unTLm>aHAT+D(*oE{`t*rfOG~TQ zXBDw6dox(yjd_kb>xaM#MJO`i4^PQ*28P!J1QZ%|wzMlN`dCwZ74*aq@bKa9*84N_ z^%m79Yo&6h#H&n3Jnk+lpkF)FX=^vw5H~DMtMT&j$y;^{{N6fQjqrGUIOMR%1c@+6 z2NC!AyB|O#pLbsRd7z-gC*(TF?VW;z(On17W^!Sm4UeRjx!HQRc~8CdIhsYIWn=}D z-(923H=Ozf1*@^CNXf{=pZ3Ydao1T6N)Wb^c6O>UOkm89Y*S=eET8Z_d<0;T8lPld zlCPy}D4?SjJn^eM16L%J%e0H2U~Y%T6+(vsf+M#BJ8eP2O2BObf`{K-)E@%A$gR@5 zp8>vtfP!Ik6uO>ku@n8r{>63|-^Io0qNp+pIW^y5D|8TokkD&@8(%RQ$UJ@0qxXb+Nd-qqamtlrEzKmNur zYOIFG^LMMHb%>oVS3^exB=nA+d?8c;49^kD#+3-%Q%ZocNz?FpCbIYDbe>uoIc2(G zu6yQxXw?gfO3KOA$tMhsy$%#Kgoh8*@33Nw2)DsWd&5>*RN{9m_T7G>8vKh=dNc0hfq5oQ~he z952-Agm~S4z4Fe9Wwo6Z1?jN|n1!BK)4vb>{vpw_+=h~mu-RZ~Nw+L}+q)nyZ}34R zM7qVLYzw>Vboa68yY5uGH}3x3ns;IF`YE7Erk#)lt>oM@8;@(~dd~Ut%aXHBM#R<1 znnEQ4A;~@}w#n!{I-|6nyYu*Ej$=a|8fgb6CKAq?SPIj*DVO_2-gKAD6TsI~T2j(Z ze@e3TXp`cRY_Z|=U4IIjCEd&MC2E@6YAbu>8~RUDl2}{>=(ID%Ap=jqCkdZtDvaA{Z2tqZQ-yoM6Vexvb3cb9wNQsD$s4*Rje`sD!cU@}U(al`-5s+6_ncVtD z`J%HYa7O+@mmpv={E0)~Q)y{=d2EJ$>^D^I3n|0{knqbBEV6W23L6;})mp2m4=b_S z>}hP$Lg$LZ(Z`gYK7+>1)2%J40~PB?fDK4c;o2fe`z3#i1obQoLD_9`YI4M$FDcng zIK%VY`ObWjakA9>LlaKS?yBz#se-^|0G{|0l(QZ1yH+Nh zl<|s*;7eMD?|h|rKkFBXf^*1H?SD)pO&QvkIBFP9>_)lw7=7(IEZh1wmIa35B8$B#%>K%;r2 z*ZDTpR~}dJG+@DGo_#J$4`kbE4F0Eg1YMn!nLgBPmg=FElj75FPv%~XCs1+OFi{A+ zAGId#irprW9Vb-%T~{ACZDk#IMp=-(9ULN@+S<`$Oa~Ad+lXs+AIYoMvI-zZ{QN5h zZ6&GA0hJj=aOaD)Z!b2oIEZw~zOr}7-|5}+Ked83ngBeezi1#$m+t}AY2XoF35xbp zp9?OEove_~lxVQ*Bi7HEr$dbZL48pJpY9&e#aeraF6!lyv7O<}T?_y5^3;Jhlh|-c zQCDvaBc#;j_b-{s0E#ui=d}F^i;jS*%!GKQ`nI>kc%sHS-vwHs(*h_ap`hyA**i;kbC3WqhX%yn2x<)RZb*-1m@q86#m-d{3gT=pRb3r!<( z+(geZDGiHykY*coQj6xBNk^vV{kFMmsHsK9ej(6XO@Z%{XKCyZc-8;Z)&T_GaKnvs zZynHOD$bEzWB}?j?&Xb85|*<4k3qYQJlgiIpH{TqH@Dp{;2N12u&m>N(O$02rdHIH z>v0FKI3GAgU3VB3VI8JgkutOmaJwFuF}7bhz_D*_Z_ne}wr?5)72NDU1{Yl2E!0`g z4-q(-&DGbjUCnz!Bdd;|K6}1cNZ>oWHSP#y%w&~5yoIvxRi^hckKa_hZLa%1@?Az`|$t5J4BweR*dz`#(Pr9_2Pk=wr) ze^jkHSyrgk=ToSnh?com)l@X(SawLI*MBTCryt=1@_&RS^om>dS(GE_9E8tQsDmT$ zwyLIX5wR{F8y`y*SUqWX6dpJk_grpvsrL^I2{jy%qQu8~M_tM}f_!RM*seuY-S^$Qo!%mMTUpZ{srUx=L++3ToR$MuEjE&s;2}rP|2O zz#_x7@*N)(bvrzE?RLlKA=o#6jPH_lZEJQuR;kFg(|_^FjtQuSc*ml% z(CUs@ud5O`6A-YF0dH4Wrdv0;P|G#Y?0kr)*FHRlgv;UfMlh7V6B6I48l{xbZG7r^ zHPt*6Z*tb;VKTg)Uo&&pAJLe+2w6~GUfxa3UkDoY%gA`2z}QUyY;*mn27*j2oyaLQ zWUhD1?9B}id1i}!ZV3D1MyI~rhi`3%-iqUbooMMTD>a;N?vC1@Kj?BfxWN(Z;UL6Z z?fHC`JC_ZnTHIKz7_J13_?}?9p{QOH_7*r-9UPaCDqupE0aq=&a@Rx{uW0a-rGVE$ z#R6#8q+cyd#&_sBqAz7XE3Mb|5D8=OHpZ<9z01Rk1cQJh^83%>yFPIi{BW^ajVrZV z99*3}2Q*5KB~6&3rsmq_5EpS^>y-m=uW`_$tQIT)W@^M+(YtTIt&8a5tEoX{6QS=$ z&54L?&6UNFSBS@|HkCexlTTn*`9H|s&+bkRok$uzYWYCx8{IIW$ zP~}mFzpevYY+nDIBJk8{x=UDV*}r1A&yVg~1Pd1OihEVN-ijcG`bi?ipjLi;wr0z{ zsVVXFElP@*M)3>93onblCktId7y^sMcV^!NzUO5b6Ds2m52~9^4@{ZMwcz`_bo#4p87nqk9umfeg+%|B@*2Z+ zQ+A8t#>_-Qw~olRm7ql4BMVx(rLiO$FZ2K$Edthvl*Ga<&p2T%(rxpRRF2E2IQxun zra^SHh;E=sh-pR8aG?KFH?Wv@ZX54!KL>gI?KVP@=Ef1qZb(((0U_BNzg2YpItaYt z8I8TS+Xzla=4F!*aH+gAx!)?%H4xgh9W&MV3ttR9ohIX5dbhi~JhfkEf!plg++js_ z3G$MXP8qP2c`veOzxi&8D9`|TV~YcUDnYQ)c?i>LQ_`-3d6MgVt2E^RXUtu+vD0!o zdgTFw-pwB9`eYrjeZSW1GzE5@+ACA<@!)CjzH(tMfBH%fw%CTD-A()SWVXU!RS`@H zmR372A0ixbH~p*qT$0=M!Knzl(0f!NF@6lcaW+c6=Z{6uOb$JjifdKlNusUieT(c) zfPpyS_{>bKL?pTkx}ct$TFY+$;-x!b*(SdYuV+*4KwKHPk8v z&8r)gL1fJ<52qstz0$=RW3emJ_jSRrXtinUg97>=YFgWaB0eCupYifM)n-T;M56ys z|M9aYSxaJ!J4ElKQRcL~!9yW=9Ahm2s06?v5Sa1wFYsrVpIh$Jo$#&iTk+LpZV-^$ zR26mw@yYW|Pzc-s6}^@9?h;O8vi#Q%^{JRFw2`yE`$cSuK{`T1s+zq&xMU1Dc;>Db z3BAc?>YEyCHI#7o?=;)--5J(mUvd4Ycnm!wy|>xIc6Hz^UG)7Mth)M8(Py;#EO3kI zf{xn?Cc~N6VY(G(2CCC!P|UTiMGF_y!{!Ui@A01*_Tsv`4p4cnJxr3QKYv3j$;UTi{OK>TWu<21^Xjtrh^ zps*Y?PTf<^JL()COZ+i!bV7LGIP>6@B8Kb`>1XR=mTU(Sk8&lQwK2tDJRT3d&xM&;X+@P zd^!YU-YP2{q(Y~4e7jh_F%cD?PoS+@GN3kL!;w+8f#)R6jANCFy|lyE;{efRTWxOP z)Mt@0&yJVhJ)#h}t1a;2I;~2j_!V+NNwD|z3~ObnQg_4jtxGt*^GB&F(Y6Zfw}cl$ zSD}tO3sRI$ENW}h49GO7MFEkqSk8@5WSy9-dA_mRedjWA z4f79VJimuq?qXhGU>_fi{v9ZA-U;Aliuvrq+6YRhlNn|mS-Q57Qi_42pdu#4MEq*S4WMY`rLaUfqMMmMh z;d7?TdYnvYEF7vBei_)@?!EQZ+jXq&a}#4f$$vZM|Mbp+ zywJ5Ho`>qN_fanrMzb%12BZ2b9*~FL5VP!77X~TZa_#hb&Ad?W4(3WWTjmll1{9uY zzH#LauB{rlIFB66(9t^1P0hI058;^6)MP1b!noWTg<5gR1xn|S`Cd)*>nCNUn;W`C zjVUwg)zcm2Wofx#lH##=O*|(Lf5zJj)7>Kq>a=nrctj^kSSY zvPP@(B3ez-Dw?_Gesyq}#znXo_15;DJreP_vU_1MS&38*sQO?ydt?_a;*HpUT(_H% zNieBC%6(Z|>6}*|qKaWLTtq8O6FI6T9oc?GBl?Xb;?Di9U^A&g6U{@1Fv_dDvY}GgW?8kT`8=0=33z=A|k)c zWWb@6$+P_YUEbdwlvKzZoZg9+Abh8sV)gNU8B$Xp>7UZmo~`eW_`Eo8v|qh_^C^}X z*UdVFkIU^WH}*QQLi>X`%dU^sQmv8o(8#>KeaA;4S76nlujXmZPAV@bI&RZaTQ5B= z55K6Rlk+~PW8c-^lmPEk3uZT@D`@b?*x*WiQhlK;kWm?HbY{N!wYj#XB>u zfJD44tMVGfd+qzC2v(Y8#b)}m6p*m*M`3$O`bccM^m;iofJ0udh9K8%PFh${N`mMRAy4Jpr8RsmKX9P-QVI#~|dz`R4| zDld~1BN;e&x)<00g{4!2UJ7q7Q^Lx0Tn}@ej0tqZZGAdi+D2Zy!qpaDhkO)xxPdwP zp-;=*UMl2zRnwz^{9%(^M_S|~4+Q0^2^j@leF7p%vPJIqc1MNx>y0^P{kB4$w z?QdErUqc)?hO($;F*e0>+PoSm{l>j24${ZvA^pxufQR}A2OI$zeBb)7ub`L>G0)GX zJN#Ks_%vlG3LZv}eqo$L4 z9LhT;Q<*uqn?y2}II_Uy{wNq0#>c)-?YXz!PrujzZtf!ZiE*xR;E?aAr6g(B>({Yg z(69o1VCY9JB@ABkoVShl8i3BIXt*$!9G*rxxiSB}*o}nD>*?wm*Rl4@1tTFD=GwYk z3)1mlN4cM_2GH^c&>I2fQPk1;j}JZveSD~GfqH=+t=wL<(zNTxg0 z<3#!23HM)T@971>h-YJCM@fI_YkszI91b`H+&B9jhF`7r^M87@{lJ7v2?^6B{^9hf zf<$Z(P^ikA!+O6zZCD)`c#x(5KX>eZdb0mL2wFhF5#{xLZTV{k`u7!7OHk!iK3+}) zzdf-3G#sj+WHgJb(C?Mcf+~Oibi2p<_k8xR(!QY)`NiRwo&@||`hVY4uoN1OsOSEC z!u=l&1S(gV6l4>&(Ix2LE7wFs6%4!H%)R{GCVAo@n^>eZA^u)@)f*x|)Wd#>-CxJl zpN}pX4YJ8Jrz6(ymH*%0Aegj@t#=puT3_GRyDZ0IdUBM<{MIyJ5LFnOyUdWi$di^^ zri;}HtBWAW=KY<}9_#N0hEt95dk-D)mdYir-r_oC! Roles*. - -[float] -=== Create an index pattern - -An index pattern is the glue that connects {kib} to your {es} data. Create an -index pattern whenever you load your own data into {kib}. To get started, -click *Create index pattern*, and then follow the guided steps. Refer to -<> for the types of index patterns -that you can create. - -[float] -=== Manage your index pattern - -To view the fields and associated data types in an index pattern, click its name in -the *Index patterns* overview. - -[role="screenshot"] -image::management/index-patterns/images/new-index-pattern.png["Index files and data types"] +Kibana provides these field formatters: -Use the icons to perform the following actions: - -* [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users -aware of which index pattern is the default. The first pattern -you create is automatically designated as the default pattern. The default -index pattern is loaded when you open *Discover*. +* <> +* <> +* <> +* <> -* *Refresh the index fields list.* You can refresh the index fields list to -pick up any newly-added fields. Doing so also resets the {kib} popularity counters -for the fields. The popularity counters are used in *Discover* to sort fields in lists. +To format a field: -* [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of -Saved Objects in {kib}. You will not be able to recover field formatters, -scripted fields, source filters, and field popularity data associated with the index pattern. -Deleting an index pattern does -not remove any indices or data documents from {es}. +. Open the main menu, and click *Stack Management > Index Patterns*. +. Click the index pattern that contains the field you want to format. +. Find the field you want to format and click the edit icon (image:management/index-patterns/images/edit_icon.png[]). +. Select a format and fill in the details. + -WARNING: Deleting an index pattern breaks all visualizations, saved searches, and -other saved objects that reference the pattern. - -[float] -=== Edit a field - -To edit a field's properties, click the edit icon -image:management/index-patterns/images/edit_icon.png[] in the detail view. -You can set the field's format and popularity value. +[role="screenshot"] +image:management/index-patterns/images/edit-field-format.png["Edit field format"] -Kibana has field formatters for the following field types: -* <> -* <> -* <> -* <> [[field-formatters-string]] === String field formatters From 26f79a6a2971f95f1fc1b24691c457a9aebe09aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Fri, 6 Nov 2020 00:30:47 +0100 Subject: [PATCH 10/81] [Security Solution] Unskip Overview cypress tests (#82782) --- .../security_solution/cypress/integration/overview.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index dafcabb8e1e8df..69094cad7456e9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -11,8 +11,7 @@ import { loginAndWaitForPage } from '../tasks/login'; import { OVERVIEW_URL } from '../urls/navigation'; -// Failing: See https://github.com/elastic/kibana/issues/81848 -describe.skip('Overview Page', () => { +describe('Overview Page', () => { it('Host stats render with correct values', () => { cy.stubSearchStrategyApi('overview_search_strategy'); loginAndWaitForPage(OVERVIEW_URL); From 8cdf56636aa5fd7453922714cd0ce01040d103d4 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Thu, 5 Nov 2020 17:41:07 -0700 Subject: [PATCH 11/81] Adds cloud links to user popover (#66825) Co-authored-by: Ryan Keairns --- x-pack/plugins/cloud/kibana.json | 2 +- x-pack/plugins/cloud/public/index.ts | 2 +- x-pack/plugins/cloud/public/mocks.ts | 18 +++ x-pack/plugins/cloud/public/plugin.ts | 28 +++- .../plugins/cloud/public/user_menu_links.ts | 38 +++++ x-pack/plugins/cloud/server/config.ts | 2 + x-pack/plugins/security/public/index.ts | 1 + x-pack/plugins/security/public/mocks.ts | 7 + .../security/public/nav_control/index.mock.ts | 14 ++ .../security/public/nav_control/index.ts | 3 +- .../nav_control/nav_control_component.scss | 11 ++ .../nav_control_component.test.tsx | 38 +++++ .../nav_control/nav_control_component.tsx | 139 ++++++++++++------ .../nav_control/nav_control_service.tsx | 39 ++++- .../plugins/security/public/plugin.test.tsx | 7 +- x-pack/plugins/security/public/plugin.tsx | 4 +- 16 files changed, 292 insertions(+), 61 deletions(-) create mode 100644 x-pack/plugins/cloud/public/mocks.ts create mode 100644 x-pack/plugins/cloud/public/user_menu_links.ts create mode 100644 x-pack/plugins/security/public/nav_control/index.mock.ts create mode 100644 x-pack/plugins/security/public/nav_control/nav_control_component.scss diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json index 27b35bcbdd88b9..9bca2f30bd23cf 100644 --- a/x-pack/plugins/cloud/kibana.json +++ b/x-pack/plugins/cloud/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "cloud"], - "optionalPlugins": ["usageCollection", "home"], + "optionalPlugins": ["usageCollection", "home", "security"], "server": true, "ui": true } diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts index 39ef5f452c18b8..680b2f1ad2bd65 100644 --- a/x-pack/plugins/cloud/public/index.ts +++ b/x-pack/plugins/cloud/public/index.ts @@ -7,7 +7,7 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { CloudPlugin } from './plugin'; -export { CloudSetup } from './plugin'; +export { CloudSetup, CloudConfigType } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new CloudPlugin(initializerContext); } diff --git a/x-pack/plugins/cloud/public/mocks.ts b/x-pack/plugins/cloud/public/mocks.ts new file mode 100644 index 00000000000000..bafebbca4ecdd8 --- /dev/null +++ b/x-pack/plugins/cloud/public/mocks.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. + */ + +function createSetupMock() { + return { + cloudId: 'mock-cloud-id', + isCloudEnabled: true, + resetPasswordUrl: 'reset-password-url', + accountUrl: 'account-url', + }; +} + +export const cloudMock = { + createSetup: createSetupMock, +}; diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index 45005f3f5e4227..bc410b89c30e7a 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -6,40 +6,51 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { i18n } from '@kbn/i18n'; +import { SecurityPluginStart } from '../../security/public'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { ELASTIC_SUPPORT_LINK } from '../common/constants'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { createUserMenuLinks } from './user_menu_links'; -interface CloudConfigType { +export interface CloudConfigType { id?: string; resetPasswordUrl?: string; deploymentUrl?: string; + accountUrl?: string; } interface CloudSetupDependencies { home?: HomePublicPluginSetup; } +interface CloudStartDependencies { + security?: SecurityPluginStart; +} + export interface CloudSetup { cloudId?: string; cloudDeploymentUrl?: string; isCloudEnabled: boolean; + resetPasswordUrl?: string; + accountUrl?: string; } export class CloudPlugin implements Plugin { private config!: CloudConfigType; + private isCloudEnabled: boolean; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); + this.isCloudEnabled = false; } public async setup(core: CoreSetup, { home }: CloudSetupDependencies) { const { id, resetPasswordUrl, deploymentUrl } = this.config; - const isCloudEnabled = getIsCloudEnabled(id); + this.isCloudEnabled = getIsCloudEnabled(id); if (home) { - home.environment.update({ cloud: isCloudEnabled }); - if (isCloudEnabled) { + home.environment.update({ cloud: this.isCloudEnabled }); + if (this.isCloudEnabled) { home.tutorials.setVariable('cloud', { id, resetPasswordUrl }); } } @@ -47,11 +58,11 @@ export class CloudPlugin implements Plugin { return { cloudId: id, cloudDeploymentUrl: deploymentUrl, - isCloudEnabled, + isCloudEnabled: this.isCloudEnabled, }; } - public start(coreStart: CoreStart) { + public start(coreStart: CoreStart, { security }: CloudStartDependencies) { const { deploymentUrl } = this.config; coreStart.chrome.setHelpSupportUrl(ELASTIC_SUPPORT_LINK); if (deploymentUrl) { @@ -63,5 +74,10 @@ export class CloudPlugin implements Plugin { href: deploymentUrl, }); } + + if (security && this.isCloudEnabled) { + const userMenuLinks = createUserMenuLinks(this.config); + security.navControlService.addUserMenuLinks(userMenuLinks); + } } } diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts new file mode 100644 index 00000000000000..15e2f14e885ba2 --- /dev/null +++ b/x-pack/plugins/cloud/public/user_menu_links.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { UserMenuLink } from '../../security/public'; +import { CloudConfigType } from '.'; + +export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => { + const { resetPasswordUrl, accountUrl } = config; + const userMenuLinks = [] as UserMenuLink[]; + + if (resetPasswordUrl) { + userMenuLinks.push({ + label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { + defaultMessage: 'Cloud profile', + }), + iconType: 'logoCloud', + href: resetPasswordUrl, + order: 100, + }); + } + + if (accountUrl) { + userMenuLinks.push({ + label: i18n.translate('xpack.cloud.userMenuLinks.accountLinkText', { + defaultMessage: 'Account & Billing', + }), + iconType: 'gear', + href: accountUrl, + order: 200, + }); + } + + return userMenuLinks; +}; diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts index ff8a2c5acdf9ab..eaa4ab7a482dd6 100644 --- a/x-pack/plugins/cloud/server/config.ts +++ b/x-pack/plugins/cloud/server/config.ts @@ -23,6 +23,7 @@ const configSchema = schema.object({ apm: schema.maybe(apmConfigSchema), resetPasswordUrl: schema.maybe(schema.string()), deploymentUrl: schema.maybe(schema.string()), + accountUrl: schema.maybe(schema.string()), }); export type CloudConfigType = TypeOf; @@ -32,6 +33,7 @@ export const config: PluginConfigDescriptor = { id: true, resetPasswordUrl: true, deploymentUrl: true, + accountUrl: true, }, schema: configSchema, }; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index 8016c942240601..d0382c22ed3c67 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -16,6 +16,7 @@ import { export { SecurityPluginSetup, SecurityPluginStart }; export { AuthenticatedUser } from '../common/model'; export { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; +export { UserMenuLink } from '../public/nav_control'; export const plugin: PluginInitializer< SecurityPluginSetup, diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts index 33c1d1446afba2..26a759ca522679 100644 --- a/x-pack/plugins/security/public/mocks.ts +++ b/x-pack/plugins/security/public/mocks.ts @@ -7,6 +7,7 @@ import { authenticationMock } from './authentication/index.mock'; import { createSessionTimeoutMock } from './session/session_timeout.mock'; import { licenseMock } from '../common/licensing/index.mock'; +import { navControlServiceMock } from './nav_control/index.mock'; function createSetupMock() { return { @@ -15,7 +16,13 @@ function createSetupMock() { license: licenseMock.create(), }; } +function createStartMock() { + return { + navControlService: navControlServiceMock.createStart(), + }; +} export const securityMock = { createSetup: createSetupMock, + createStart: createStartMock, }; diff --git a/x-pack/plugins/security/public/nav_control/index.mock.ts b/x-pack/plugins/security/public/nav_control/index.mock.ts new file mode 100644 index 00000000000000..1cd10810d7c8f1 --- /dev/null +++ b/x-pack/plugins/security/public/nav_control/index.mock.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 { SecurityNavControlServiceStart } from '.'; + +export const navControlServiceMock = { + createStart: (): jest.Mocked => ({ + getUserMenuLinks$: jest.fn(), + addUserMenuLinks: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/nav_control/index.ts b/x-pack/plugins/security/public/nav_control/index.ts index 2b0af1a45d05a9..737ae500546987 100644 --- a/x-pack/plugins/security/public/nav_control/index.ts +++ b/x-pack/plugins/security/public/nav_control/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { SecurityNavControlService } from './nav_control_service'; +export { SecurityNavControlService, SecurityNavControlServiceStart } from './nav_control_service'; +export { UserMenuLink } from './nav_control_component'; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.scss b/x-pack/plugins/security/public/nav_control/nav_control_component.scss new file mode 100644 index 00000000000000..a3e04b08cfac20 --- /dev/null +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.scss @@ -0,0 +1,11 @@ +.chrNavControl__userMenu { + .euiContextMenuPanelTitle { + // Uppercased by default, override to match actual username + text-transform: none; + } + + .euiContextMenuItem { + // Temp fix for EUI issue https://github.com/elastic/eui/issues/3092 + line-height: normal; + } +} \ No newline at end of file diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index c1c6a9f69b6ec9..1da91e80d062de 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { BehaviorSubject } from 'rxjs'; import { shallowWithIntl, nextTick, mountWithIntl } from 'test_utils/enzyme_helpers'; import { SecurityNavControl } from './nav_control_component'; import { AuthenticatedUser } from '../../common/model'; @@ -17,6 +18,7 @@ describe('SecurityNavControl', () => { user: new Promise(() => {}) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = shallowWithIntl(); @@ -42,6 +44,7 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = shallowWithIntl(); @@ -70,6 +73,7 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = mountWithIntl(); @@ -91,6 +95,7 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = mountWithIntl(); @@ -107,4 +112,37 @@ describe('SecurityNavControl', () => { expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); }); + + it('renders a popover with additional user menu links registered by other plugins', async () => { + const props = { + user: Promise.resolve({ full_name: 'foo' }) as Promise, + editProfileUrl: '', + logoutUrl: '', + userMenuLinks$: new BehaviorSubject([ + { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, + { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, + { label: 'link3', href: 'path-to-link-3', iconType: 'empty', order: 3 }, + ]), + }; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); + expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); + expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(0); + expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(0); + expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(0); + expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); + + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(1); + expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); + expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(1); + expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(1); + expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(1); + expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 3ddabb0dc55f8c..c22308fa8a43e0 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -7,38 +7,52 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; - +import { Observable, Subscription } from 'rxjs'; import { EuiAvatar, - EuiFlexGroup, - EuiFlexItem, EuiHeaderSectionItemButton, - EuiLink, - EuiText, - EuiSpacer, EuiPopover, EuiLoadingSpinner, + EuiIcon, + EuiContextMenu, + EuiContextMenuPanelItemDescriptor, + IconType, + EuiText, } from '@elastic/eui'; import { AuthenticatedUser } from '../../common/model'; +import './nav_control_component.scss'; + +export interface UserMenuLink { + label: string; + iconType: IconType; + href: string; + order?: number; +} + interface Props { user: Promise; editProfileUrl: string; logoutUrl: string; + userMenuLinks$: Observable; } interface State { isOpen: boolean; authenticatedUser: AuthenticatedUser | null; + userMenuLinks: UserMenuLink[]; } export class SecurityNavControl extends Component { + private subscription?: Subscription; + constructor(props: Props) { super(props); this.state = { isOpen: false, authenticatedUser: null, + userMenuLinks: [], }; props.user.then((authenticatedUser) => { @@ -48,6 +62,18 @@ export class SecurityNavControl extends Component { }); } + componentDidMount() { + this.subscription = this.props.userMenuLinks$.subscribe(async (userMenuLinks) => { + this.setState({ userMenuLinks }); + }); + } + + componentWillUnmount() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + onMenuButtonClick = () => { if (!this.state.authenticatedUser) { return; @@ -66,13 +92,13 @@ export class SecurityNavControl extends Component { render() { const { editProfileUrl, logoutUrl } = this.props; - const { authenticatedUser } = this.state; + const { authenticatedUser, userMenuLinks } = this.state; - const name = + const username = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; const buttonContents = authenticatedUser ? ( - + ) : ( ); @@ -92,6 +118,60 @@ export class SecurityNavControl extends Component { ); + const profileMenuItem = { + name: ( + + ), + icon: , + href: editProfileUrl, + 'data-test-subj': 'profileLink', + }; + + const logoutMenuItem = { + name: ( + + ), + icon: , + href: logoutUrl, + 'data-test-subj': 'logoutLink', + }; + + const items: EuiContextMenuPanelItemDescriptor[] = []; + + items.push(profileMenuItem); + + if (userMenuLinks.length) { + const userMenuLinkMenuItems = userMenuLinks + .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) + .map(({ label, iconType, href }: UserMenuLink) => ({ + name: {label}, + icon: , + href, + 'data-test-subj': `userMenuLink__${label}`, + })); + + items.push(...userMenuLinkMenuItems, { + isSeparator: true, + key: 'securityNavControlComponent__userMenuLinksSeparator', + }); + } + + items.push(logoutMenuItem); + + const panels = [ + { + id: 0, + title: username, + items, + }, + ]; + return ( { repositionOnScroll closePopover={this.closeMenu} panelPaddingSize="none" + buffer={0} > -
- - - - - - - -

{name}

-
- - - - - - - - - - - - - - - - - - - - -
-
+
+
); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index aa3ec2e47469d0..4ae64d667ce293 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -4,12 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Subscription } from 'rxjs'; +import { sortBy } from 'lodash'; +import { Observable, Subscription, BehaviorSubject, ReplaySubject } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; import { CoreStart } from 'src/core/public'; + import ReactDOM from 'react-dom'; import React from 'react'; + import { SecurityLicense } from '../../common/licensing'; -import { SecurityNavControl } from './nav_control_component'; +import { SecurityNavControl, UserMenuLink } from './nav_control_component'; import { AuthenticationServiceSetup } from '../authentication'; interface SetupDeps { @@ -22,6 +26,18 @@ interface StartDeps { core: CoreStart; } +export interface SecurityNavControlServiceStart { + /** + * Returns an Observable of the array of user menu links registered by other plugins + */ + getUserMenuLinks$: () => Observable; + + /** + * Registers the provided user menu links to be displayed in the user menu in the global nav + */ + addUserMenuLinks: (newUserMenuLink: UserMenuLink[]) => void; +} + export class SecurityNavControlService { private securityLicense!: SecurityLicense; private authc!: AuthenticationServiceSetup; @@ -31,13 +47,16 @@ export class SecurityNavControlService { private securityFeaturesSubscription?: Subscription; + private readonly stop$ = new ReplaySubject(1); + private userMenuLinks$ = new BehaviorSubject([]); + public setup({ securityLicense, authc, logoutUrl }: SetupDeps) { this.securityLicense = securityLicense; this.authc = authc; this.logoutUrl = logoutUrl; } - public start({ core }: StartDeps) { + public start({ core }: StartDeps): SecurityNavControlServiceStart { this.securityFeaturesSubscription = this.securityLicense.features$.subscribe( ({ showLinks }) => { const isAnonymousPath = core.http.anonymousPaths.isAnonymous(window.location.pathname); @@ -49,6 +68,14 @@ export class SecurityNavControlService { } } ); + + return { + getUserMenuLinks$: () => + this.userMenuLinks$.pipe(map(this.sortUserMenuLinks), takeUntil(this.stop$)), + addUserMenuLinks: (userMenuLink: UserMenuLink[]) => { + this.userMenuLinks$.next(userMenuLink); + }, + }; } public stop() { @@ -57,6 +84,7 @@ export class SecurityNavControlService { this.securityFeaturesSubscription = undefined; } this.navControlRegistered = false; + this.stop$.next(); } private registerSecurityNavControl( @@ -72,6 +100,7 @@ export class SecurityNavControlService { user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/security/account'), logoutUrl: this.logoutUrl, + userMenuLinks$: this.userMenuLinks$, }; ReactDOM.render( @@ -86,4 +115,8 @@ export class SecurityNavControlService { this.navControlRegistered = true; } + + private sortUserMenuLinks(userMenuLinks: UserMenuLink[]) { + return sortBy(userMenuLinks, 'order'); + } } diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index d86d4812af5e3f..6f5a2a031a7b22 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -97,7 +97,12 @@ describe('Security Plugin', () => { data: {} as DataPublicPluginStart, features: {} as FeaturesPluginStart, }) - ).toBeUndefined(); + ).toEqual({ + navControlService: { + getUserMenuLinks$: expect.any(Function), + addUserMenuLinks: expect.any(Function), + }, + }); }); it('starts Management Service if `management` plugin is available', () => { diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 700653c4cecb8e..f94772c43dd896 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -146,11 +146,13 @@ export class SecurityPlugin public start(core: CoreStart, { management, securityOss }: PluginStartDependencies) { this.sessionTimeout.start(); - this.navControlService.start({ core }); this.securityCheckupService.start({ securityOssStart: securityOss, docLinks: core.docLinks }); + if (management) { this.managementService.start({ capabilities: core.application.capabilities }); } + + return { navControlService: this.navControlService.start({ core }) }; } public stop() { From f3599fec4c2edd97fa555679a154cb5a8b48096b Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 5 Nov 2020 19:45:10 -0500 Subject: [PATCH 12/81] [SECURITY SOLUTIONS] Keep context of timeline when switching tabs in security solutions (#82237) * try to keep timeline context when switching tabs * fix popover * simpler solution to keep timelien context between tabs * fix timeline context with relative date * allow update on the kql bar when opening new timeline * keep detail view in context when savedObjectId of the timeline does not chnage * remove redux solution and just KISS it * add unit test for the popover * add test on timeline context cache * final commit -> to fix context of timeline between tabs * keep timerange kind to absolute when refreshing * fix bug today/thiw week to be absolute and not relative * add unit test for absolute date for today and this week * fix absolute today/this week on timeline * fix refresh between page and timeline when link * clean up * remove nit Co-authored-by: Patryk Kopycinski --- .../common/components/query_bar/index.tsx | 5 +- .../common/components/search_bar/index.tsx | 34 ++- .../super_date_picker/index.test.tsx | 20 +- .../components/super_date_picker/index.tsx | 63 ++-- .../super_date_picker/selectors.test.ts | 69 +++-- .../components/super_date_picker/selectors.ts | 15 +- .../public/common/store/inputs/actions.ts | 2 + .../public/common/store/inputs/model.ts | 4 +- .../public/common/store/inputs/reducer.ts | 23 +- .../common/store/sourcerer/selectors.ts | 21 +- .../field_renderers/field_renderers.test.tsx | 42 +++ .../field_renderers/field_renderers.tsx | 8 +- .../__snapshots__/timeline.test.tsx.snap | 1 + .../components/timeline/body/events/index.tsx | 1 - .../timeline/body/events/stateful_event.tsx | 276 ++++++------------ .../timelines/components/timeline/index.tsx | 11 +- .../components/timeline/timeline.test.tsx | 1 + .../components/timeline/timeline.tsx | 3 + .../containers/active_timeline_context.ts | 75 +++++ .../timelines/containers/index.test.tsx | 210 +++++++++++++ .../public/timelines/containers/index.tsx | 141 +++++++-- .../timeline/epic_local_storage.test.tsx | 1 + .../timelines/store/timeline/helpers.ts | 33 ++- 23 files changed, 748 insertions(+), 311 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx index d68ab3a171151e..7555f6e7342145 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx @@ -62,7 +62,10 @@ export const QueryBar = memo( const [draftQuery, setDraftQuery] = useState(filterQuery); useEffect(() => { - // Reset draftQuery when `Create new timeline` is clicked + setDraftQuery(filterQuery); + }, [filterQuery]); + + useEffect(() => { if (filterQueryDraft == null) { setDraftQuery(filterQuery); } diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index 2dc44fd48e66dc..acc01ac4f76aa8 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -132,7 +132,7 @@ export const SearchBarComponent = memo( if (!isStateUpdated) { // That mean we are doing a refresh! - if (isQuickSelection) { + if (isQuickSelection && payload.dateRange.to !== payload.dateRange.from) { updateSearchBar.updateTime = true; updateSearchBar.end = payload.dateRange.to; updateSearchBar.start = payload.dateRange.from; @@ -313,7 +313,7 @@ const makeMapStateToProps = () => { fromStr: getFromStrSelector(inputsRange), filterQuery: getFilterQuerySelector(inputsRange), isLoading: getIsLoadingSelector(inputsRange), - queries: getQueriesSelector(inputsRange), + queries: getQueriesSelector(state, id), savedQuery: getSavedQuerySelector(inputsRange), start: getStartSelector(inputsRange), toStr: getToStrSelector(inputsRange), @@ -351,15 +351,27 @@ export const dispatchUpdateSearch = (dispatch: Dispatch) => ({ const fromDate = formatDate(start); let toDate = formatDate(end, { roundUp: true }); if (isQuickSelection) { - dispatch( - inputsActions.setRelativeRangeDatePicker({ - id, - fromStr: start, - toStr: end, - from: fromDate, - to: toDate, - }) - ); + if (end === start) { + dispatch( + inputsActions.setAbsoluteRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } else { + dispatch( + inputsActions.setRelativeRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } } else { toDate = formatDate(end); dispatch( diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx index 956ee4b05f9d65..bcb10f8fd26c33 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx @@ -139,7 +139,7 @@ describe('SIEM Super Date Picker', () => { expect(store.getState().inputs.global.timerange.toStr).toBe('now'); }); - test('Make Sure it is Today date', () => { + test('Make Sure it is Today date is an absolute date', () => { wrapper .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() @@ -151,8 +151,22 @@ describe('SIEM Super Date Picker', () => { .first() .simulate('click'); wrapper.update(); - expect(store.getState().inputs.global.timerange.fromStr).toBe('now/d'); - expect(store.getState().inputs.global.timerange.toStr).toBe('now/d'); + expect(store.getState().inputs.global.timerange.kind).toBe('absolute'); + }); + + test('Make Sure it is This Week date is an absolute date', () => { + wrapper + .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .first() + .simulate('click'); + wrapper.update(); + + wrapper + .find('[data-test-subj="superDatePickerCommonlyUsed_This_week"]') + .first() + .simulate('click'); + wrapper.update(); + expect(store.getState().inputs.global.timerange.kind).toBe('absolute'); }); test('Make Sure to (end date) is superior than from (start date)', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx index 4443d24531b22e..97e023176647f8 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx @@ -91,12 +91,12 @@ export const SuperDatePickerComponent = React.memo( toStr, updateReduxTime, }) => { - const [isQuickSelection, setIsQuickSelection] = useState(true); const [recentlyUsedRanges, setRecentlyUsedRanges] = useState( [] ); const onRefresh = useCallback( ({ start: newStart, end: newEnd }: OnRefreshProps): void => { + const isQuickSelection = newStart.includes('now') || newEnd.includes('now'); const { kqlHaveBeenUpdated } = updateReduxTime({ end: newEnd, id, @@ -117,12 +117,13 @@ export const SuperDatePickerComponent = React.memo( refetchQuery(queries); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [end, id, isQuickSelection, kqlQuery, start, timelineId] + [end, id, kqlQuery, queries, start, timelineId, updateReduxTime] ); const onRefreshChange = useCallback( ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => { + const isQuickSelection = + (fromStr != null && fromStr.includes('now')) || (toStr != null && toStr.includes('now')); if (duration !== refreshInterval) { setDuration({ id, duration: refreshInterval }); } @@ -137,27 +138,22 @@ export const SuperDatePickerComponent = React.memo( refetchQuery(queries); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, isQuickSelection, duration, policy, toStr] + [fromStr, toStr, duration, policy, setDuration, id, stopAutoReload, startAutoReload, queries] ); - const refetchQuery = (newQueries: inputsModel.GlobalGraphqlQuery[]) => { + const refetchQuery = (newQueries: inputsModel.GlobalQuery[]) => { newQueries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); }; const onTimeChange = useCallback( - ({ - start: newStart, - end: newEnd, - isQuickSelection: newIsQuickSelection, - isInvalid, - }: OnTimeChangeProps) => { + ({ start: newStart, end: newEnd, isInvalid }: OnTimeChangeProps) => { + const isQuickSelection = newStart.includes('now') || newEnd.includes('now'); if (!isInvalid) { updateReduxTime({ end: newEnd, id, isInvalid, - isQuickSelection: newIsQuickSelection, + isQuickSelection, kql: kqlQuery, start: newStart, timelineId, @@ -174,15 +170,13 @@ export const SuperDatePickerComponent = React.memo( ]; setRecentlyUsedRanges(newRecentlyUsedRanges); - setIsQuickSelection(newIsQuickSelection); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [recentlyUsedRanges, kqlQuery] + [updateReduxTime, id, kqlQuery, timelineId, recentlyUsedRanges] ); - const endDate = kind === 'relative' ? toStr : new Date(end).toISOString(); - const startDate = kind === 'relative' ? fromStr : new Date(start).toISOString(); + const endDate = toStr != null ? toStr : new Date(end).toISOString(); + const startDate = fromStr != null ? fromStr : new Date(start).toISOString(); const [quickRanges] = useUiSetting$(DEFAULT_TIMEPICKER_QUICK_RANGES); const commonlyUsedRanges = isEmpty(quickRanges) @@ -232,15 +226,27 @@ export const dispatchUpdateReduxTime = (dispatch: Dispatch) => ({ const fromDate = formatDate(start); let toDate = formatDate(end, { roundUp: true }); if (isQuickSelection) { - dispatch( - inputsActions.setRelativeRangeDatePicker({ - id, - fromStr: start, - toStr: end, - from: fromDate, - to: toDate, - }) - ); + if (end === start) { + dispatch( + inputsActions.setAbsoluteRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } else { + dispatch( + inputsActions.setRelativeRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } } else { toDate = formatDate(end); dispatch( @@ -284,6 +290,7 @@ export const makeMapStateToProps = () => { const getToStrSelector = toStrSelector(); return (state: State, { id }: OwnProps) => { const inputsRange: InputsRange = getOr({}, `inputs.${id}`, state); + return { duration: getDurationSelector(inputsRange), end: getEndSelector(inputsRange), @@ -292,7 +299,7 @@ export const makeMapStateToProps = () => { kind: getKindSelector(inputsRange), kqlQuery: getKqlQuerySelector(inputsRange) as inputsModel.GlobalKqlQuery, policy: getPolicySelector(inputsRange), - queries: getQueriesSelector(inputsRange) as inputsModel.GlobalGraphqlQuery[], + queries: getQueriesSelector(state, id), start: getStartSelector(inputsRange), toStr: getToStrSelector(inputsRange), }; diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.test.ts b/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.test.ts index 7cb4ea9ada93fd..ee19aef717f4f8 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.test.ts @@ -17,6 +17,8 @@ import { } from './selectors'; import { InputsRange, AbsoluteTimeRange, RelativeTimeRange } from '../../store/inputs/model'; import { cloneDeep } from 'lodash/fp'; +import { mockGlobalState } from '../../mock'; +import { State } from '../../store'; describe('selectors', () => { let absoluteTime: AbsoluteTimeRange = { @@ -42,6 +44,8 @@ describe('selectors', () => { filters: [], }; + let mockState: State = mockGlobalState; + const getPolicySelector = policySelector(); const getDurationSelector = durationSelector(); const getKindSelector = kindSelector(); @@ -75,6 +79,8 @@ describe('selectors', () => { }, filters: [], }; + + mockState = mockGlobalState; }); describe('#policySelector', () => { @@ -375,34 +381,61 @@ describe('selectors', () => { describe('#queriesSelector', () => { test('returns the same reference given the same identical input twice', () => { - const result1 = getQueriesSelector(inputState); - const result2 = getQueriesSelector(inputState); + const myMock = { + ...mockState, + inputs: { + ...mockState.inputs, + global: inputState, + }, + }; + const result1 = getQueriesSelector(myMock, 'global'); + const result2 = getQueriesSelector(myMock, 'global'); expect(result1).toBe(result2); }); test('DOES NOT return the same reference given different input twice but with different deep copies since the query is not a primitive', () => { - const clone = cloneDeep(inputState); - const result1 = getQueriesSelector(inputState); - const result2 = getQueriesSelector(clone); + const myMock = { + ...mockState, + inputs: { + ...mockState.inputs, + global: inputState, + }, + }; + const clone = cloneDeep(myMock); + const result1 = getQueriesSelector(myMock, 'global'); + const result2 = getQueriesSelector(clone, 'global'); expect(result1).not.toBe(result2); }); test('returns a different reference even if the contents are the same since query is an array and not a primitive', () => { - const result1 = getQueriesSelector(inputState); - const change: InputsRange = { - ...inputState, - queries: [ - { - loading: false, - id: '1', - inspect: { dsl: [], response: [] }, - isInspected: false, - refetch: null, - selectedInspectIndex: 0, + const myMock = { + ...mockState, + inputs: { + ...mockState.inputs, + global: inputState, + }, + }; + const result1 = getQueriesSelector(myMock, 'global'); + const myMockChange: State = { + ...myMock, + inputs: { + ...mockState.inputs, + global: { + ...mockState.inputs.global, + queries: [ + { + loading: false, + id: '1', + inspect: { dsl: [], response: [] }, + isInspected: false, + refetch: null, + selectedInspectIndex: 0, + }, + ], }, - ], + }, }; - const result2 = getQueriesSelector(change); + const result2 = getQueriesSelector(myMockChange, 'global'); expect(result1).not.toBe(result2); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.ts b/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.ts index d4b990890ebbad..840dd1f4a6b9f2 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/selectors.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEmpty } from 'lodash'; import { createSelector } from 'reselect'; +import { State } from '../../store'; +import { InputsModelId } from '../../store/inputs/constants'; import { Policy, InputsRange, TimeRange, GlobalQuery } from '../../store/inputs/model'; export const getPolicy = (inputState: InputsRange): Policy => inputState.policy; @@ -13,6 +16,16 @@ export const getTimerange = (inputState: InputsRange): TimeRange => inputState.t export const getQueries = (inputState: InputsRange): GlobalQuery[] => inputState.queries; +export const getGlobalQueries = (state: State, id: InputsModelId): GlobalQuery[] => { + const inputsRange = state.inputs[id]; + return !isEmpty(inputsRange.linkTo) + ? inputsRange.linkTo.reduce((acc, linkToId) => { + const linkToIdInputsRange: InputsRange = state.inputs[linkToId]; + return [...acc, ...linkToIdInputsRange.queries]; + }, inputsRange.queries) + : inputsRange.queries; +}; + export const policySelector = () => createSelector(getPolicy, (policy) => policy.kind); export const durationSelector = () => createSelector(getPolicy, (policy) => policy.duration); @@ -31,7 +44,7 @@ export const isLoadingSelector = () => createSelector(getQueries, (queries) => queries.some((i) => i.loading === true)); export const queriesSelector = () => - createSelector(getQueries, (queries) => queries.filter((q) => q.id !== 'kql')); + createSelector(getGlobalQueries, (queries) => queries.filter((q) => q.id !== 'kql')); export const kqlQuerySelector = () => createSelector(getQueries, (queries) => queries.find((q) => q.id === 'kql')); diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts index 5d00882f778c07..db911365972157 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts @@ -16,6 +16,8 @@ export const setAbsoluteRangeDatePicker = actionCreator<{ id: InputsModelId; from: string; to: string; + fromStr?: string; + toStr?: string; }>('SET_ABSOLUTE_RANGE_DATE_PICKER'); export const setTimelineRangeDatePicker = actionCreator<{ diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/model.ts b/x-pack/plugins/security_solution/public/common/store/inputs/model.ts index a8db48c7b31bba..f4e2c2f28f4776 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/model.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/model.ts @@ -11,8 +11,8 @@ import { Query, Filter, SavedQuery } from '../../../../../../../src/plugins/data export interface AbsoluteTimeRange { kind: 'absolute'; - fromStr: undefined; - toStr: undefined; + fromStr?: string; + toStr?: string; from: string; to: string; } diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts b/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts index a94f0f6ca24ee5..59ae029a9207e7 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts @@ -149,16 +149,19 @@ export const inputsReducer = reducerWithInitialState(initialInputsState) }, }; }) - .case(setAbsoluteRangeDatePicker, (state, { id, from, to }) => { - const timerange: TimeRange = { - kind: 'absolute', - fromStr: undefined, - toStr: undefined, - from, - to, - }; - return updateInputTimerange(id, timerange, state); - }) + .case( + setAbsoluteRangeDatePicker, + (state, { id, from, to, fromStr = undefined, toStr = undefined }) => { + const timerange: TimeRange = { + kind: 'absolute', + fromStr, + toStr, + from, + to, + }; + return updateInputTimerange(id, timerange, state); + } + ) .case(setRelativeRangeDatePicker, (state, { id, fromStr, from, to, toStr }) => { const timerange: TimeRange = { kind: 'relative', diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts index e7bd6234cb207b..6ebc00133c0cdc 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts @@ -86,18 +86,25 @@ export const defaultIndexNamesSelector = () => { return mapStateToProps; }; -const EXLCUDE_ELASTIC_CLOUD_INDEX = '-*elastic-cloud-logs-*'; +const EXCLUDE_ELASTIC_CLOUD_INDEX = '-*elastic-cloud-logs-*'; export const getSourcererScopeSelector = () => { const getScopesSelector = scopesSelector(); - const mapStateToProps = (state: State, scopeId: SourcererScopeName): ManageScope => ({ - ...getScopesSelector(state)[scopeId], - selectedPatterns: getScopesSelector(state)[scopeId].selectedPatterns.some( + const mapStateToProps = (state: State, scopeId: SourcererScopeName): ManageScope => { + const selectedPatterns = getScopesSelector(state)[scopeId].selectedPatterns.some( (index) => index === 'logs-*' ) - ? [...getScopesSelector(state)[scopeId].selectedPatterns, EXLCUDE_ELASTIC_CLOUD_INDEX] - : getScopesSelector(state)[scopeId].selectedPatterns, - }); + ? [...getScopesSelector(state)[scopeId].selectedPatterns, EXCLUDE_ELASTIC_CLOUD_INDEX] + : getScopesSelector(state)[scopeId].selectedPatterns; + return { + ...getScopesSelector(state)[scopeId], + selectedPatterns, + indexPattern: { + ...getScopesSelector(state)[scopeId].indexPattern, + title: selectedPatterns.join(), + }, + }; + }; return mapStateToProps; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx index bf89cc7fa9084a..1d8d0f789d6b7e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx @@ -20,6 +20,7 @@ import { reputationRenderer, DefaultFieldRenderer, DEFAULT_MORE_MAX_HEIGHT, + DefaultFieldRendererOverflow, MoreContainer, } from './field_renderers'; import { mockData } from '../../../network/components/details/mock'; @@ -330,4 +331,45 @@ describe('Field Renderers', () => { expect(render).toHaveBeenCalledTimes(2); }); }); + + describe('DefaultFieldRendererOverflow', () => { + const idPrefix = 'prefix-1'; + const rowItems = ['item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7']; + + test('it should render the length of items after the overflowIndexStart', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(' ,+2 More'); + expect(wrapper.find('[data-test-subj="more-container"]').first().exists()).toBe(false); + }); + + test('it should render the items after overflowIndexStart in the popover', () => { + const wrapper = mount( + + + + ); + + wrapper.find('button').first().simulate('click'); + wrapper.update(); + expect(wrapper.find('.euiPopover').first().exists()).toBe(true); + expect(wrapper.find('[data-test-subj="more-container"]').first().text()).toEqual( + 'item6item7' + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx index cb913287b24d89..7f543ab859bb4a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx @@ -260,12 +260,12 @@ MoreContainer.displayName = 'MoreContainer'; export const DefaultFieldRendererOverflow = React.memo( ({ idPrefix, moreMaxHeight, overflowIndexStart = 5, render, rowItems }) => { const [isOpen, setIsOpen] = useState(false); - const handleClose = useCallback(() => setIsOpen(false), []); + const togglePopover = useCallback(() => setIsOpen((currentIsOpen) => !currentIsOpen), []); const button = useMemo( () => ( <> {' ,'} - + {`+${rowItems.length - overflowIndexStart} `} ), - [handleClose, overflowIndexStart, rowItems.length] + [togglePopover, overflowIndexStart, rowItems.length] ); return ( @@ -284,7 +284,7 @@ export const DefaultFieldRendererOverflow = React.memo = ({ columnHeaders={columnHeaders} columnRenderers={columnRenderers} containerElementRef={containerElementRef} - disableSensorVisibility={data != null && data.length < 101} docValueFields={docValueFields} event={event} eventIdToNoteIds={eventIdToNoteIds} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 4f385a46564833..83e824aa2450a6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -6,7 +6,6 @@ import React, { useRef, useState, useCallback } from 'react'; import uuid from 'uuid'; -import VisibilitySensor from 'react-visibility-sensor'; import { BrowserFields, DocValueFields } from '../../../../../common/containers/source'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; @@ -19,7 +18,6 @@ import { import { Note } from '../../../../../common/lib/note'; import { ColumnHeaderOptions, TimelineModel } from '../../../../../timelines/store/timeline/model'; import { AddNoteToEvent, UpdateNote } from '../../../notes/helpers'; -import { SkeletonRow } from '../../skeleton_row'; import { OnColumnResized, OnPinEvent, @@ -38,6 +36,8 @@ import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; import { inputsModel } from '../../../../../common/store'; +import { TimelineId } from '../../../../../../common/types/timeline'; +import { activeTimeline } from '../../../../containers/active_timeline_context'; interface Props { actionsColumnWidth: number; @@ -46,7 +46,6 @@ interface Props { browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; columnRenderers: ColumnRenderer[]; - disableSensorVisibility: boolean; docValueFields: DocValueFields[]; event: TimelineItem; eventIdToNoteIds: Readonly>; @@ -73,33 +72,6 @@ export const getNewNoteId = (): string => uuid.v4(); const emptyDetails: TimelineEventsDetailsItem[] = []; -/** - * This is the default row height whenever it is a plain row renderer and not a custom row height. - * We use this value when we do not know the height of a particular row. - */ -const DEFAULT_ROW_HEIGHT = '32px'; - -/** - * This is the top offset in pixels of the top part of the timeline. The UI area where you do your - * drag and drop and filtering. It is a positive number in pixels of _PART_ of the header but not - * the entire header. We leave room for some rows to render behind the drag and drop so they might be - * visible by the time the user scrolls upwards. All other DOM elements are replaced with their "blank" - * rows. - */ -const TOP_OFFSET = 50; - -/** - * This is the bottom offset in pixels of the bottom part of the timeline. The UI area right below the - * timeline which is the footer. Since the footer is so incredibly small we don't have enough room to - * render around 5 rows below the timeline to get the user the best chance of always scrolling without seeing - * "blank rows". The negative number is to give the bottom of the browser window a bit of invisible space to - * keep around 5 rows rendering below it. All other DOM elements are replaced with their "blank" - * rows. - */ -const BOTTOM_OFFSET = -500; - -const VISIBILITY_SENSOR_OFFSET = { top: TOP_OFFSET, bottom: BOTTOM_OFFSET }; - const emptyNotes: string[] = []; const EventsTrSupplementContainerWrapper = React.memo(({ children }) => { @@ -116,7 +88,6 @@ const StatefulEventComponent: React.FC = ({ containerElementRef, columnHeaders, columnRenderers, - disableSensorVisibility = true, docValueFields, event, eventIdToNoteIds, @@ -138,7 +109,9 @@ const StatefulEventComponent: React.FC = ({ toggleColumn, updateNote, }) => { - const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({}); + const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>( + timelineId === TimelineId.active ? activeTimeline.getExpandedEventIds() : {} + ); const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); const { status: timelineStatus } = useShallowEqualSelector( (state) => state.timeline.timelineById[timelineId] @@ -148,21 +121,21 @@ const StatefulEventComponent: React.FC = ({ docValueFields, indexName: event._index!, eventId: event._id, - skip: !expanded[event._id], + skip: !expanded || !expanded[event._id], }); const onToggleShowNotes = useCallback(() => { const eventId = event._id; - setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] }); - }, [event, showNotes]); + setShowNotes((prevShowNotes) => ({ ...prevShowNotes, [eventId]: !prevShowNotes[eventId] })); + }, [event]); const onToggleExpanded = useCallback(() => { const eventId = event._id; - setExpanded({ - ...expanded, - [eventId]: !expanded[eventId], - }); - }, [event, expanded]); + setExpanded((prevExpanded) => ({ ...prevExpanded, [eventId]: !prevExpanded[eventId] })); + if (timelineId === TimelineId.active) { + activeTimeline.toggleExpandedEvent(eventId); + } + }, [event._id, timelineId]); const associateNote = useCallback( (noteId: string) => { @@ -174,152 +147,87 @@ const StatefulEventComponent: React.FC = ({ [addNoteToEvent, event, isEventPinned, onPinEvent] ); - // Number of current columns plus one for actions. - const columnCount = columnHeaders.length + 1; - - const VisibilitySensorContent = useCallback( - ({ isVisible }) => { - if (isVisible || disableSensorVisibility) { - return ( - - - - - - - - - {getRowRenderer(event.ecs, rowRenderers).renderRow({ - browserFields, - data: event.ecs, - timelineId, - })} - - - - - - - ); - } else { - // Height place holder for visibility detection as well as re-rendering sections. - const height = - divElement.current != null && divElement.current!.clientHeight - ? `${divElement.current!.clientHeight}px` - : DEFAULT_ROW_HEIGHT; - - return ; - } - }, - [ - actionsColumnWidth, - associateNote, - browserFields, - columnCount, - columnHeaders, - columnRenderers, - detailsData, - disableSensorVisibility, - event._id, - event.data, - event.ecs, - eventIdToNoteIds, - expanded, - getNotesByIds, - isEventPinned, - isEventViewer, - loading, - loadingEventIds, - onColumnResized, - onPinEvent, - onRowSelected, - onToggleExpanded, - onToggleShowNotes, - onUnPinEvent, - onUpdateColumns, - refetch, - onRuleChange, - rowRenderers, - selectedEventIds, - showCheckboxes, - showNotes, - timelineId, - timelineStatus, - toggleColumn, - updateNote, - ] - ); - return ( - - {VisibilitySensorContent} - + + + + + + + + {getRowRenderer(event.ecs, rowRenderers).renderRow({ + browserFields, + data: event.ecs, + timelineId, + })} + + + + + + ); }; 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 0c7b1e0cdecd5e..35d31e034e7f38 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 @@ -27,6 +27,11 @@ export interface OwnProps { export type Props = OwnProps & PropsFromRedux; +const isTimerangeSame = (prevProps: Props, nextProps: Props) => + prevProps.end === nextProps.end && + prevProps.start === nextProps.start && + prevProps.timerangeKind === nextProps.timerangeKind; + const StatefulTimelineComponent = React.memo( ({ columns, @@ -51,6 +56,7 @@ const StatefulTimelineComponent = React.memo( start, status, timelineType, + timerangeKind, updateItemsPerPage, upsertColumn, usersViewing, @@ -125,13 +131,14 @@ const StatefulTimelineComponent = React.memo( status={status} toggleColumn={toggleColumn} timelineType={timelineType} + timerangeKind={timerangeKind} usersViewing={usersViewing} /> ); }, (prevProps, nextProps) => { return ( - prevProps.end === nextProps.end && + isTimerangeSame(prevProps, nextProps) && prevProps.graphEventId === nextProps.graphEventId && prevProps.id === nextProps.id && prevProps.isLive === nextProps.isLive && @@ -142,7 +149,6 @@ const StatefulTimelineComponent = React.memo( prevProps.kqlQueryExpression === nextProps.kqlQueryExpression && prevProps.show === nextProps.show && prevProps.showCallOutUnauthorizedMsg === nextProps.showCallOutUnauthorizedMsg && - prevProps.start === nextProps.start && prevProps.timelineType === nextProps.timelineType && prevProps.status === nextProps.status && deepEqual(prevProps.columns, nextProps.columns) && @@ -209,6 +215,7 @@ const makeMapStateToProps = () => { start: input.timerange.from, status, timelineType, + timerangeKind: input.timerange.kind, }; }; return mapStateToProps; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx index 630a71693d182c..7fc269c954ac40 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx @@ -116,6 +116,7 @@ describe('Timeline', () => { start: startDate, status: TimelineStatus.active, timelineType: TimelineType.default, + timerangeKind: 'absolute', toggleColumn: jest.fn(), usersViewing: ['elastic'], }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx index b860011c2ddaff..f7c76c110ac3f1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx @@ -112,6 +112,7 @@ export interface Props { start: string; status: TimelineStatusLiteral; timelineType: TimelineType; + timerangeKind: 'absolute' | 'relative'; toggleColumn: (column: ColumnHeaderOptions) => void; usersViewing: string[]; } @@ -143,6 +144,7 @@ export const TimelineComponent: React.FC = ({ status, sort, timelineType, + timerangeKind, toggleColumn, usersViewing, }) => { @@ -212,6 +214,7 @@ export const TimelineComponent: React.FC = ({ startDate: start, skip: !canQueryTimeline, sort: timelineQuerySortField, + timerangeKind, }); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts b/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts new file mode 100644 index 00000000000000..50bf8b37adf28d --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { TimelineArgs } from '.'; +import { TimelineEventsAllRequestOptions } from '../../../common/search_strategy/timeline'; + +/* + * Future Engineer + * This class is just there to manage temporarily the reload of the active timeline when switching tabs + * because of the bootstrap of the security solution app, we will always trigger the query + * to avoid it we will cache its request and response so we can go back where the user was before switching tabs + * + * !!! Important !!! this is just there until, we will have a better way to bootstrap the app + * I did not want to put in the store because I was feeling it will feel less temporarily and I did not want other engineer using it + * + */ +class ActiveTimelineEvents { + private _activePage: number = 0; + private _expandedEventIds: Record = {}; + private _pageName: string = ''; + private _request: TimelineEventsAllRequestOptions | null = null; + private _response: TimelineArgs | null = null; + + getActivePage() { + return this._activePage; + } + + setActivePage(activePage: number) { + this._activePage = activePage; + } + + getExpandedEventIds() { + return this._expandedEventIds; + } + + toggleExpandedEvent(eventId: string) { + this._expandedEventIds = { + ...this._expandedEventIds, + [eventId]: !this._expandedEventIds[eventId], + }; + } + + setExpandedEventIds(expandedEventIds: Record) { + this._expandedEventIds = expandedEventIds; + } + + getPageName() { + return this._pageName; + } + + setPageName(pageName: string) { + this._pageName = pageName; + } + + getRequest() { + return this._request; + } + + setRequest(req: TimelineEventsAllRequestOptions) { + this._request = req; + } + + getResponse() { + return this._response; + } + + setResponse(resp: TimelineArgs | null) { + this._response = resp; + } +} + +export const activeTimeline = new ActiveTimelineEvents(); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx new file mode 100644 index 00000000000000..a5f8300546b5bd --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { renderHook, act } from '@testing-library/react-hooks'; +import { initSortDefault, TimelineArgs, useTimelineEvents, UseTimelineEventsProps } from '.'; +import { SecurityPageName } from '../../../common/constants'; +import { TimelineId } from '../../../common/types/timeline'; +import { mockTimelineData } from '../../common/mock'; +import { useRouteSpy } from '../../common/utils/route/use_route_spy'; + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); + +const mockEvents = mockTimelineData.filter((i, index) => index <= 11); + +const mockSearch = jest.fn(); + +jest.mock('../../common/lib/kibana', () => ({ + useKibana: jest.fn().mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, + data: { + search: { + search: jest.fn().mockImplementation((args) => { + mockSearch(); + return { + subscribe: jest.fn().mockImplementation(({ next }) => { + next({ + isRunning: false, + isPartial: false, + inspect: { + dsl: [], + response: [], + }, + edges: mockEvents.map((item) => ({ node: item })), + pageInfo: { + activePage: 0, + totalPages: 10, + }, + rawResponse: {}, + totalCount: mockTimelineData.length, + }); + return { unsubscribe: jest.fn() }; + }), + }; + }), + }, + }, + notifications: { + toasts: { + addWarning: jest.fn(), + }, + }, + }, + }), +})); + +const mockUseRouteSpy: jest.Mock = useRouteSpy as jest.Mock; +jest.mock('../../common/utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn(), +})); + +mockUseRouteSpy.mockReturnValue([ + { + pageName: SecurityPageName.overview, + detailName: undefined, + tabName: undefined, + search: '', + pathName: '/overview', + }, +]); + +describe('useTimelineEvents', () => { + beforeEach(() => { + mockSearch.mockReset(); + }); + + const startDate: string = '2020-07-07T08:20:18.966Z'; + const endDate: string = '3000-01-01T00:00:00.000Z'; + const props: UseTimelineEventsProps = { + docValueFields: [], + endDate: '', + id: TimelineId.active, + indexNames: ['filebeat-*'], + fields: [], + filterQuery: '', + startDate: '', + limit: 25, + sort: initSortDefault, + skip: false, + }; + + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + UseTimelineEventsProps, + [boolean, TimelineArgs] + >((args) => useTimelineEvents(args), { + initialProps: { ...props }, + }); + + // useEffect on params request + await waitForNextUpdate(); + expect(result.current).toEqual([ + false, + { + events: [], + id: TimelineId.active, + inspect: result.current[1].inspect, + loadPage: result.current[1].loadPage, + pageInfo: result.current[1].pageInfo, + refetch: result.current[1].refetch, + totalCount: -1, + updatedAt: 0, + }, + ]); + }); + }); + + test('happy path query', async () => { + await act(async () => { + const { result, waitForNextUpdate, rerender } = renderHook< + UseTimelineEventsProps, + [boolean, TimelineArgs] + >((args) => useTimelineEvents(args), { + initialProps: { ...props }, + }); + + // useEffect on params request + await waitForNextUpdate(); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitForNextUpdate(); + + expect(mockSearch).toHaveBeenCalledTimes(1); + expect(result.current).toEqual([ + false, + { + events: mockEvents, + id: TimelineId.active, + inspect: result.current[1].inspect, + loadPage: result.current[1].loadPage, + pageInfo: result.current[1].pageInfo, + refetch: result.current[1].refetch, + totalCount: 31, + updatedAt: result.current[1].updatedAt, + }, + ]); + }); + }); + + test('Mock cache for active timeline when switching page', async () => { + await act(async () => { + const { result, waitForNextUpdate, rerender } = renderHook< + UseTimelineEventsProps, + [boolean, TimelineArgs] + >((args) => useTimelineEvents(args), { + initialProps: { ...props }, + }); + + // useEffect on params request + await waitForNextUpdate(); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitForNextUpdate(); + + mockUseRouteSpy.mockReturnValue([ + { + pageName: SecurityPageName.timelines, + detailName: undefined, + tabName: undefined, + search: '', + pathName: '/timelines', + }, + ]); + + expect(mockSearch).toHaveBeenCalledTimes(1); + + expect(result.current).toEqual([ + false, + { + events: mockEvents, + id: TimelineId.active, + inspect: result.current[1].inspect, + loadPage: result.current[1].loadPage, + pageInfo: result.current[1].pageInfo, + refetch: result.current[1].refetch, + totalCount: 31, + updatedAt: result.current[1].updatedAt, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 65f8a3dc78e4db..5f92596f033114 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -30,6 +30,9 @@ import { } from '../../../common/search_strategy'; import { InspectResponse } from '../../types'; import * as i18n from './translations'; +import { TimelineId } from '../../../common/types/timeline'; +import { useRouteSpy } from '../../common/utils/route/use_route_spy'; +import { activeTimeline } from './active_timeline_context'; export interface TimelineArgs { events: TimelineItem[]; @@ -44,7 +47,7 @@ export interface TimelineArgs { type LoadPage = (newActivePage: number) => void; -interface UseTimelineEventsProps { +export interface UseTimelineEventsProps { docValueFields?: DocValueFields[]; filterQuery?: ESQuery | string; skip?: boolean; @@ -55,17 +58,26 @@ interface UseTimelineEventsProps { limit: number; sort: SortField; startDate: string; + timerangeKind?: 'absolute' | 'relative'; } const getTimelineEvents = (timelineEdges: TimelineEdges[]): TimelineItem[] => timelineEdges.map((e: TimelineEdges) => e.node); const ID = 'timelineEventsQuery'; -const initSortDefault = { +export const initSortDefault = { field: '@timestamp', direction: Direction.asc, }; +function usePreviousRequest(value: TimelineEventsAllRequestOptions | null) { + const ref = useRef(value); + useEffect(() => { + ref.current = value; + }); + return ref.current; +} + export const useTimelineEvents = ({ docValueFields, endDate, @@ -77,13 +89,17 @@ export const useTimelineEvents = ({ limit, sort = initSortDefault, skip = false, + timerangeKind, }: UseTimelineEventsProps): [boolean, TimelineArgs] => { + const [{ pageName }] = useRouteSpy(); const dispatch = useDispatch(); const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const [loading, setLoading] = useState(false); - const [activePage, setActivePage] = useState(0); + const [activePage, setActivePage] = useState( + id === TimelineId.active ? activeTimeline.getActivePage() : 0 + ); const [timelineRequest, setTimelineRequest] = useState( !skip ? { @@ -106,6 +122,7 @@ export const useTimelineEvents = ({ } : null ); + const prevTimelineRequest = usePreviousRequest(timelineRequest); const clearSignalsState = useCallback(() => { if (id != null && detectionsTimelineIds.some((timelineId) => timelineId === id)) { @@ -117,18 +134,31 @@ export const useTimelineEvents = ({ const wrappedLoadPage = useCallback( (newActivePage: number) => { clearSignalsState(); + + if (id === TimelineId.active) { + activeTimeline.setExpandedEventIds({}); + activeTimeline.setActivePage(newActivePage); + } + setActivePage(newActivePage); }, - [clearSignalsState] + [clearSignalsState, id] ); + const refetchGrid = useCallback(() => { + if (refetch.current != null) { + refetch.current(); + } + wrappedLoadPage(0); + }, [wrappedLoadPage]); + const [timelineResponse, setTimelineResponse] = useState({ - id: ID, + id, inspect: { dsl: [], response: [], }, - refetch: refetch.current, + refetch: refetchGrid, totalCount: -1, pageInfo: { activePage: 0, @@ -141,15 +171,13 @@ export const useTimelineEvents = ({ const timelineSearch = useCallback( (request: TimelineEventsAllRequestOptions | null) => { - if (request == null) { + if (request == null || pageName === '') { return; } - let didCancel = false; const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); - const searchSubscription$ = data.search .search(request, { strategy: 'securitySolutionTimelineSearchStrategy', @@ -157,26 +185,39 @@ export const useTimelineEvents = ({ }) .subscribe({ next: (response) => { - if (isCompleteResponse(response)) { - if (!didCancel) { - setLoading(false); - setTimelineResponse((prevResponse) => ({ - ...prevResponse, - events: getTimelineEvents(response.edges), - inspect: getInspectResponse(response, prevResponse.inspect), - pageInfo: response.pageInfo, - refetch: refetch.current, - totalCount: response.totalCount, - updatedAt: Date.now(), - })); - } - searchSubscription$.unsubscribe(); - } else if (isErrorResponse(response)) { - if (!didCancel) { - setLoading(false); + try { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + + setTimelineResponse((prevResponse) => { + const newTimelineResponse = { + ...prevResponse, + events: getTimelineEvents(response.edges), + inspect: getInspectResponse(response, prevResponse.inspect), + pageInfo: response.pageInfo, + totalCount: response.totalCount, + updatedAt: Date.now(), + }; + if (id === TimelineId.active) { + activeTimeline.setExpandedEventIds({}); + activeTimeline.setPageName(pageName); + activeTimeline.setRequest(request); + activeTimeline.setResponse(newTimelineResponse); + } + return newTimelineResponse; + }); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + notifications.toasts.addWarning(i18n.ERROR_TIMELINE_EVENTS); + searchSubscription$.unsubscribe(); } + } catch { notifications.toasts.addWarning(i18n.ERROR_TIMELINE_EVENTS); - searchSubscription$.unsubscribe(); } }, error: (msg) => { @@ -189,15 +230,43 @@ export const useTimelineEvents = ({ }, }); }; + + if ( + id === TimelineId.active && + activeTimeline.getPageName() !== '' && + pageName !== activeTimeline.getPageName() + ) { + activeTimeline.setPageName(pageName); + + abortCtrl.current.abort(); + setLoading(false); + refetch.current = asyncSearch.bind(null, activeTimeline.getRequest()); + setTimelineResponse((prevResp) => { + const resp = activeTimeline.getResponse(); + if (resp != null) { + return { + ...resp, + refetch: refetchGrid, + loadPage: wrappedLoadPage, + }; + } + return prevResp; + }); + if (activeTimeline.getResponse() != null) { + return; + } + } + abortCtrl.current.abort(); asyncSearch(); refetch.current = asyncSearch; + return () => { didCancel = true; abortCtrl.current.abort(); }; }, - [data.search, notifications.toasts] + [data.search, id, notifications.toasts, pageName, refetchGrid, wrappedLoadPage] ); useEffect(() => { @@ -251,8 +320,10 @@ export const useTimelineEvents = ({ if (activePage !== newActivePage) { setActivePage(newActivePage); + if (id === TimelineId.active) { + activeTimeline.setActivePage(newActivePage); + } } - if ( !skip && !skipQueryForDetectionsPage(id, indexNames) && @@ -263,12 +334,13 @@ export const useTimelineEvents = ({ return prevRequest; }); }, [ + dispatch, + indexNames, activePage, docValueFields, endDate, filterQuery, id, - indexNames, limit, startDate, sort, @@ -277,8 +349,13 @@ export const useTimelineEvents = ({ ]); useEffect(() => { - timelineSearch(timelineRequest); - }, [timelineRequest, timelineSearch]); + if ( + id !== TimelineId.active || + timerangeKind === 'absolute' || + !deepEqual(prevTimelineRequest, timelineRequest) + ) + timelineSearch(timelineRequest); + }, [id, prevTimelineRequest, timelineRequest, timelineSearch, timerangeKind]); return [loading, timelineResponse]; }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index 1992b1f88f0641..d6597df71526f1 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -102,6 +102,7 @@ describe('epicLocalStorage', () => { status: TimelineStatus.active, sort, timelineType: TimelineType.default, + timerangeKind: 'absolute', toggleColumn: jest.fn(), usersViewing: ['elastic'], }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts index 30d0796443ab50..d4e807b4a9a073 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts @@ -26,12 +26,14 @@ import { TimelineTypeLiteral, TimelineType, RowRendererId, + TimelineId, } from '../../../../common/types/timeline'; import { normalizeTimeRange } from '../../../common/components/url_state/normalize_time_range'; import { timelineDefaults } from './defaults'; import { ColumnHeaderOptions, KqlMode, TimelineModel } from './model'; import { TimelineById } from './types'; +import { activeTimeline } from '../../containers/active_timeline_context'; export const isNotNull = (value: T | null): value is T => value !== null; @@ -113,6 +115,17 @@ interface AddTimelineParams { timelineById: TimelineById; } +export const shouldResetActiveTimelineContext = ( + id: string, + oldTimeline: TimelineModel, + newTimeline: TimelineModel +) => { + if (id === TimelineId.active && oldTimeline.savedObjectId !== newTimeline.savedObjectId) { + return true; + } + return false; +}; + /** * Add a saved object timeline to the store * and default the value to what need to be if values are null @@ -121,13 +134,19 @@ export const addTimelineToStore = ({ id, timeline, timelineById, -}: AddTimelineParams): TimelineById => ({ - ...timelineById, - [id]: { - ...timeline, - isLoading: timelineById[id].isLoading, - }, -}); +}: AddTimelineParams): TimelineById => { + if (shouldResetActiveTimelineContext(id, timelineById[id], timeline)) { + activeTimeline.setActivePage(0); + activeTimeline.setExpandedEventIds({}); + } + return { + ...timelineById, + [id]: { + ...timeline, + isLoading: timelineById[id].isLoading, + }, + }; +}; interface AddNewTimelineParams { columns: ColumnHeaderOptions[]; From 1ecd12cdf32ef41a370af7064467dd3047529f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Thu, 5 Nov 2020 19:50:50 -0500 Subject: [PATCH 13/81] Add description and documentation link in alert flyout (#81526) * Add description and documentation URL in alert flyout * Add unit tests * Fix type check * Add horizontal rule * Design fixes * Fix uptime alert link * Fix uptime urls * Add anchor tag * Fix jest test failures * Fix monitoring links --- .../public/alert_types/always_firing.tsx | 1 + .../public/alert_types/astros.tsx | 1 + .../alerting/register_apm_alerts.ts | 12 +++++++ .../infra/public/alerting/inventory/index.ts | 3 ++ .../log_threshold/log_threshold_alert_type.ts | 3 ++ .../public/alerting/metric_threshold/index.ts | 3 ++ .../cpu_usage_alert/cpu_usage_alert.tsx | 3 ++ .../public/alerts/disk_usage_alert/index.tsx | 3 ++ .../alerts/legacy_alert/legacy_alert.tsx | 3 ++ .../alerts/memory_usage_alert/index.tsx | 3 ++ .../missing_monitoring_data_alert.tsx | 3 ++ .../thread_pool_rejections_alert/index.tsx | 3 ++ .../geo_threshold/index.ts | 2 ++ .../threshold/expression.tsx | 1 - .../builtin_alert_types/threshold/index.ts | 3 ++ .../sections/alert_form/alert_add.test.tsx | 1 + .../sections/alert_form/alert_edit.test.tsx | 1 + .../sections/alert_form/alert_form.test.tsx | 36 ++++++++++++++++++- .../sections/alert_form/alert_form.tsx | 29 +++++++++++++++ .../components/alerts_list.test.tsx | 1 + .../public/application/type_registry.test.ts | 1 + .../triggers_actions_ui/public/types.ts | 1 + .../__tests__/monitor_status.test.ts | 1 + .../lib/alert_types/duration_anomaly.tsx | 3 ++ .../public/lib/alert_types/monitor_status.tsx | 3 ++ .../uptime/public/lib/alert_types/tls.tsx | 3 ++ .../fixtures/plugins/alerts/public/plugin.ts | 2 ++ 27 files changed, 127 insertions(+), 2 deletions(-) diff --git a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx index 9c420f4425d04e..a5d158fca836b3 100644 --- a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx @@ -22,6 +22,7 @@ export function getAlertType(): AlertTypeModel { name: 'Always Fires', description: 'Alert when called', iconClass: 'bolt', + documentationUrl: null, alertParamsExpression: AlwaysFiringExpression, validate: (alertParams: AlwaysFiringParamsProps['alertParams']) => { const { instances } = alertParams; diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 343f6b10ef85bb..73c7dfea1263bb 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -47,6 +47,7 @@ export function getAlertType(): AlertTypeModel { name: 'People Are In Space Right Now', description: 'Alert when people are in space right now', iconClass: 'globe', + documentationUrl: null, alertParamsExpression: PeopleinSpaceExpression, validate: (alertParams: PeopleinSpaceParamsProps['alertParams']) => { const { outerSpaceCapacity, craft, op } = alertParams; 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 0eeb31927b2f59..988e335af5b7cc 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 @@ -22,6 +22,9 @@ export function registerApmAlerts( 'Alert when the number of errors in a service exceeds a defined threshold.', }), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + }, alertParamsExpression: lazy(() => import('./ErrorCountAlertTrigger')), validate: () => ({ errors: [], @@ -53,6 +56,9 @@ export function registerApmAlerts( } ), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + }, alertParamsExpression: lazy( () => import('./TransactionDurationAlertTrigger') ), @@ -87,6 +93,9 @@ export function registerApmAlerts( } ), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + }, alertParamsExpression: lazy( () => import('./TransactionErrorRateAlertTrigger') ), @@ -121,6 +130,9 @@ export function registerApmAlerts( } ), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + }, alertParamsExpression: lazy( () => import('./TransactionDurationAnomalyAlertTrigger') ), diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts index b49465db051356..d7afd73c0e3a71 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/index.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -21,6 +21,9 @@ export function createInventoryMetricAlertType(): AlertTypeModel { defaultMessage: 'Alert when the inventory exceeds a defined threshold.', }), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/infrastructure-threshold-alert.html`; + }, alertParamsExpression: React.lazy(() => import('./components/expression')), validate: validateMetricThreshold, defaultActionMessage: i18n.translate( diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts index 2e4cb2a53b6b58..60c22c42c00b6f 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts @@ -19,6 +19,9 @@ export function getAlertType(): AlertTypeModel { defaultMessage: 'Alert when the log aggregation exceeds the threshold.', }), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/logs-threshold-alert.html`; + }, alertParamsExpression: React.lazy(() => import('./components/expression_editor/editor')), validate: validateExpression, defaultActionMessage: i18n.translate( diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts index a48837792a3ccb..05c69e5ccb78bd 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts @@ -21,6 +21,9 @@ export function createMetricThresholdAlertType(): AlertTypeModel { defaultMessage: 'Alert when the metrics aggregation exceeds the threshold.', }), iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/metrics-threshold-alert.html`; + }, alertParamsExpression: React.lazy(() => import('./components/expression')), validate: validateMetricThreshold, defaultActionMessage: i18n.translate( diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index 11ba8214ff81eb..5054c47245f0fc 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -16,6 +16,9 @@ export function createCpuUsageAlertType(): AlertTypeModel { name: ALERT_DETAILS[ALERT_CPU_USAGE].label, description: ALERT_DETAILS[ALERT_CPU_USAGE].description, iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cpu-threshold`; + }, alertParamsExpression: (props: Props) => ( ), diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index 7c44e37904ec50..00b70658e4289a 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -18,6 +18,9 @@ export function createDiskUsageAlertType(): AlertTypeModel { name: ALERT_DETAILS[ALERT_DISK_USAGE].label, description: ALERT_DETAILS[ALERT_DISK_USAGE].description, iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-disk-usage-threshold`; + }, alertParamsExpression: (props: Props) => ( ), diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index ca7af2fe64e782..c8d0a7a5d49f2a 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -18,6 +18,9 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { name: LEGACY_ALERT_DETAILS[legacyAlert].label, description: LEGACY_ALERT_DETAILS[legacyAlert].description, iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/cluster-alerts.html`; + }, alertParamsExpression: () => ( diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 14fb7147179c13..062c32c7587942 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -18,6 +18,9 @@ export function createMemoryUsageAlertType(): AlertTypeModel { name: ALERT_DETAILS[ALERT_MEMORY_USAGE].label, description: ALERT_DETAILS[ALERT_MEMORY_USAGE].description, iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-jvm-memory-threshold`; + }, alertParamsExpression: (props: Props) => ( ), diff --git a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx index 4c8f00f8385c26..ec97a45a8a8005 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx @@ -16,6 +16,9 @@ export function createMissingMonitoringDataAlertType(): AlertTypeModel { name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label, description: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].description, iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-missing-monitoring-data`; + }, alertParamsExpression: (props: any) => ( ( <> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts index 9f33e2c2495c57..00d9ebbbbc0660 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts @@ -20,6 +20,8 @@ export function getAlertType(): AlertTypeModel import('./query_builder')), validate: validateExpression, requiresAppContext: false, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index 7c42c43dc79a2b..e309d97b57f341 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -281,7 +281,6 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent ) : null} -
import('./expression')), validate: validateExpression, requiresAppContext: false, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 2a69580d7185c9..d66c5ba5121b83 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -99,6 +99,7 @@ describe('alert_add', () => { iconClass: 'test', name: 'test-alert', description: 'test', + documentationUrl: null, validate: (): ValidationResult => { return { errors: {} }; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 34f9f29274f8f1..31c61f0bba7688 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -52,6 +52,7 @@ describe('alert_edit', () => { iconClass: 'test', name: 'test-alert', description: 'test', + documentationUrl: null, validate: (): ValidationResult => { return { errors: {} }; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 98eaea64797b2d..4041f6f451a23c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -31,7 +31,8 @@ describe('alert_form', () => { id: 'my-alert-type', iconClass: 'test', name: 'test-alert', - description: 'test', + description: 'Alert when testing', + documentationUrl: 'https://localhost.local/docs', validate: (): ValidationResult => { return { errors: {} }; }, @@ -59,6 +60,7 @@ describe('alert_form', () => { iconClass: 'test', name: 'non edit alert', description: 'test', + documentationUrl: null, validate: (): ValidationResult => { return { errors: {} }; }, @@ -182,6 +184,22 @@ describe('alert_form', () => { ); expect(alertTypeSelectOptions.exists()).toBeFalsy(); }); + + it('renders alert type description', async () => { + await setup(); + wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); + const alertDescription = wrapper.find('[data-test-subj="alertDescription"]'); + expect(alertDescription.exists()).toBeTruthy(); + expect(alertDescription.first().text()).toContain('Alert when testing'); + }); + + it('renders alert type documentation link', async () => { + await setup(); + wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); + const alertDocumentationLink = wrapper.find('[data-test-subj="alertDocumentationLink"]'); + expect(alertDocumentationLink.exists()).toBeTruthy(); + expect(alertDocumentationLink.first().prop('href')).toBe('https://localhost.local/docs'); + }); }); describe('alert_form create alert non alerting consumer and producer', () => { @@ -244,6 +262,7 @@ describe('alert_form', () => { iconClass: 'test', name: 'test-alert', description: 'test', + documentationUrl: null, validate: (): ValidationResult => { return { errors: {} }; }, @@ -255,6 +274,7 @@ describe('alert_form', () => { iconClass: 'test', name: 'test-alert', description: 'test', + documentationUrl: null, validate: (): ValidationResult => { return { errors: {} }; }, @@ -423,5 +443,19 @@ describe('alert_form', () => { const throttleFieldAfterUpdate = wrapper.find('[data-test-subj="throttleInput"]'); expect(throttleFieldAfterUpdate.at(1).prop('value')).toEqual(newThrottle); }); + + it('renders alert type description', async () => { + await setup(); + const alertDescription = wrapper.find('[data-test-subj="alertDescription"]'); + expect(alertDescription.exists()).toBeTruthy(); + expect(alertDescription.first().text()).toContain('Alert when testing'); + }); + + it('renders alert type documentation link', async () => { + await setup(); + const alertDocumentationLink = wrapper.find('[data-test-subj="alertDocumentationLink"]'); + expect(alertDocumentationLink.exists()).toBeTruthy(); + expect(alertDocumentationLink.first().prop('href')).toBe('https://localhost.local/docs'); + }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index bdc11fd543ee14..9a637ea750f815 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -25,6 +25,8 @@ import { EuiHorizontalRule, EuiLoadingSpinner, EuiEmptyPrompt, + EuiLink, + EuiText, } from '@elastic/eui'; import { some, filter, map, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -247,6 +249,33 @@ export const AlertForm = ({ ) : null} + {alertTypeModel?.description && ( + + + + {alertTypeModel.description}  + {alertTypeModel?.documentationUrl && ( + + + + )} + + + + )} + {AlertParamsExpressionComponent ? ( }> { return { errors: {} }; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts index 311f366df74e05..f875bcabdcde82 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts @@ -17,6 +17,7 @@ const getTestAlertType = (id?: string, name?: string, iconClass?: string) => { name: name || 'Test alert type', description: 'Test description', iconClass: iconClass || 'icon', + documentationUrl: null, validate: (): ValidationResult => { return { errors: {} }; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index bf1ff26af42e2a..1a6b68080c9a4b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -176,6 +176,7 @@ export interface AlertTypeModel name: string | JSX.Element; description: string; iconClass: string; + documentationUrl: string | ((docLinks: DocLinksStart) => string) | null; validate: (alertParams: AlertParamsType) => ValidationResult; alertParamsExpression: | React.FunctionComponent diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index 5106fcbc97bcd0..8da45276fa532f 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -204,6 +204,7 @@ describe('monitor status alert type', () => { "alertParamsExpression": [Function], "defaultActionMessage": "Monitor {{state.monitorName}} with url {{{state.monitorUrl}}} is {{state.statusMessage}} from {{state.observerLocation}}. The latest error message is {{{state.latestErrorMessage}}}", "description": "Alert when a monitor is down or an availability threshold is breached.", + "documentationUrl": [Function], "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", "name": ({ id: CLIENT_ALERT_TYPES.DURATION_ANOMALY, iconClass: 'uptimeApp', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/uptime/${docLinks.DOC_LINK_VERSION}/uptime-alerting.html`; + }, alertParamsExpression: (params: unknown) => ( ), diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 4e3d9a3c6e0ac0..43aaa26d86642a 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -31,6 +31,9 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({ ), description, iconClass: 'uptimeApp', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/uptime/${docLinks.DOC_LINK_VERSION}/uptime-alerting.html#_monitor_status_alerts`; + }, alertParamsExpression: (params: any) => ( ), diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx index 41ff08b0da97cd..83c4792e26f597 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -15,6 +15,9 @@ const TLSAlert = React.lazy(() => import('./lazy_wrapper/tls_alert')); export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.TLS, iconClass: 'uptimeApp', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/uptime/${docLinks.DOC_LINK_VERSION}/uptime-alerting.html#_tls_alerts`; + }, alertParamsExpression: (params: any) => ( ), diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index c738ce0697f750..af4aedda06ef75 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -31,6 +31,7 @@ export class AlertingFixturePlugin implements Plugin React.createElement('div', null, 'Test Always Firing'), validate: () => { return { errors: {} }; @@ -43,6 +44,7 @@ export class AlertingFixturePlugin implements Plugin React.createElement('div', null, 'Test Noop'), validate: () => { return { errors: {} }; From d6200462c6ecd8d80d47291a3d0a9b7b85e56f68 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 5 Nov 2020 19:05:41 -0600 Subject: [PATCH 14/81] Add APM OSS README (#82754) --- docs/developer/plugin-list.asciidoc | 4 +--- src/plugins/apm_oss/README.asciidoc | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/plugins/apm_oss/README.asciidoc diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 9235fc1198b12a..b59545cbb85a64 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -28,9 +28,7 @@ allowing users to configure their advanced settings, also known as uiSettings within the code. -|{kib-repo}blob/{branch}/src/plugins/apm_oss[apmOss] -|WARNING: Missing README. - +|{kib-repo}blob/{branch}/src/plugins/apm_oss/README.asciidoc[apmOss] |{kib-repo}blob/{branch}/src/plugins/bfetch/README.md[bfetch] |bfetch allows to batch HTTP requests and streams responses back. diff --git a/src/plugins/apm_oss/README.asciidoc b/src/plugins/apm_oss/README.asciidoc new file mode 100644 index 00000000000000..c3c060a99ee272 --- /dev/null +++ b/src/plugins/apm_oss/README.asciidoc @@ -0,0 +1,5 @@ +# APM OSS plugin + +OSS plugin for APM. Includes index configuration and tutorial resources. + +See <<../../x-pack/plugins/apm/readme.md,the X-Pack APM plugin README>> for information about the main APM plugin. From e378555971afeac14bead8949da95389144bafe5 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Thu, 5 Nov 2020 21:25:57 -0700 Subject: [PATCH 15/81] Revert "Adds cloud links to user popover (#66825)" (#82802) This reverts commit 8cdf56636aa5fd7453922714cd0ce01040d103d4. --- x-pack/plugins/cloud/kibana.json | 2 +- x-pack/plugins/cloud/public/index.ts | 2 +- x-pack/plugins/cloud/public/mocks.ts | 18 --- x-pack/plugins/cloud/public/plugin.ts | 28 +--- .../plugins/cloud/public/user_menu_links.ts | 38 ----- x-pack/plugins/cloud/server/config.ts | 2 - x-pack/plugins/security/public/index.ts | 1 - x-pack/plugins/security/public/mocks.ts | 7 - .../security/public/nav_control/index.mock.ts | 14 -- .../security/public/nav_control/index.ts | 3 +- .../nav_control/nav_control_component.scss | 11 -- .../nav_control_component.test.tsx | 38 ----- .../nav_control/nav_control_component.tsx | 139 ++++++------------ .../nav_control/nav_control_service.tsx | 39 +---- .../plugins/security/public/plugin.test.tsx | 7 +- x-pack/plugins/security/public/plugin.tsx | 4 +- 16 files changed, 61 insertions(+), 292 deletions(-) delete mode 100644 x-pack/plugins/cloud/public/mocks.ts delete mode 100644 x-pack/plugins/cloud/public/user_menu_links.ts delete mode 100644 x-pack/plugins/security/public/nav_control/index.mock.ts delete mode 100644 x-pack/plugins/security/public/nav_control/nav_control_component.scss diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json index 9bca2f30bd23cf..27b35bcbdd88b9 100644 --- a/x-pack/plugins/cloud/kibana.json +++ b/x-pack/plugins/cloud/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "cloud"], - "optionalPlugins": ["usageCollection", "home", "security"], + "optionalPlugins": ["usageCollection", "home"], "server": true, "ui": true } diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts index 680b2f1ad2bd65..39ef5f452c18b8 100644 --- a/x-pack/plugins/cloud/public/index.ts +++ b/x-pack/plugins/cloud/public/index.ts @@ -7,7 +7,7 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { CloudPlugin } from './plugin'; -export { CloudSetup, CloudConfigType } from './plugin'; +export { CloudSetup } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new CloudPlugin(initializerContext); } diff --git a/x-pack/plugins/cloud/public/mocks.ts b/x-pack/plugins/cloud/public/mocks.ts deleted file mode 100644 index bafebbca4ecdd8..00000000000000 --- a/x-pack/plugins/cloud/public/mocks.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -function createSetupMock() { - return { - cloudId: 'mock-cloud-id', - isCloudEnabled: true, - resetPasswordUrl: 'reset-password-url', - accountUrl: 'account-url', - }; -} - -export const cloudMock = { - createSetup: createSetupMock, -}; diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index bc410b89c30e7a..45005f3f5e4227 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -6,51 +6,40 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { i18n } from '@kbn/i18n'; -import { SecurityPluginStart } from '../../security/public'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { ELASTIC_SUPPORT_LINK } from '../common/constants'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; -import { createUserMenuLinks } from './user_menu_links'; -export interface CloudConfigType { +interface CloudConfigType { id?: string; resetPasswordUrl?: string; deploymentUrl?: string; - accountUrl?: string; } interface CloudSetupDependencies { home?: HomePublicPluginSetup; } -interface CloudStartDependencies { - security?: SecurityPluginStart; -} - export interface CloudSetup { cloudId?: string; cloudDeploymentUrl?: string; isCloudEnabled: boolean; - resetPasswordUrl?: string; - accountUrl?: string; } export class CloudPlugin implements Plugin { private config!: CloudConfigType; - private isCloudEnabled: boolean; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); - this.isCloudEnabled = false; } public async setup(core: CoreSetup, { home }: CloudSetupDependencies) { const { id, resetPasswordUrl, deploymentUrl } = this.config; - this.isCloudEnabled = getIsCloudEnabled(id); + const isCloudEnabled = getIsCloudEnabled(id); if (home) { - home.environment.update({ cloud: this.isCloudEnabled }); - if (this.isCloudEnabled) { + home.environment.update({ cloud: isCloudEnabled }); + if (isCloudEnabled) { home.tutorials.setVariable('cloud', { id, resetPasswordUrl }); } } @@ -58,11 +47,11 @@ export class CloudPlugin implements Plugin { return { cloudId: id, cloudDeploymentUrl: deploymentUrl, - isCloudEnabled: this.isCloudEnabled, + isCloudEnabled, }; } - public start(coreStart: CoreStart, { security }: CloudStartDependencies) { + public start(coreStart: CoreStart) { const { deploymentUrl } = this.config; coreStart.chrome.setHelpSupportUrl(ELASTIC_SUPPORT_LINK); if (deploymentUrl) { @@ -74,10 +63,5 @@ export class CloudPlugin implements Plugin { href: deploymentUrl, }); } - - if (security && this.isCloudEnabled) { - const userMenuLinks = createUserMenuLinks(this.config); - security.navControlService.addUserMenuLinks(userMenuLinks); - } } } diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts deleted file mode 100644 index 15e2f14e885ba2..00000000000000 --- a/x-pack/plugins/cloud/public/user_menu_links.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { UserMenuLink } from '../../security/public'; -import { CloudConfigType } from '.'; - -export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => { - const { resetPasswordUrl, accountUrl } = config; - const userMenuLinks = [] as UserMenuLink[]; - - if (resetPasswordUrl) { - userMenuLinks.push({ - label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { - defaultMessage: 'Cloud profile', - }), - iconType: 'logoCloud', - href: resetPasswordUrl, - order: 100, - }); - } - - if (accountUrl) { - userMenuLinks.push({ - label: i18n.translate('xpack.cloud.userMenuLinks.accountLinkText', { - defaultMessage: 'Account & Billing', - }), - iconType: 'gear', - href: accountUrl, - order: 200, - }); - } - - return userMenuLinks; -}; diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts index eaa4ab7a482dd6..ff8a2c5acdf9ab 100644 --- a/x-pack/plugins/cloud/server/config.ts +++ b/x-pack/plugins/cloud/server/config.ts @@ -23,7 +23,6 @@ const configSchema = schema.object({ apm: schema.maybe(apmConfigSchema), resetPasswordUrl: schema.maybe(schema.string()), deploymentUrl: schema.maybe(schema.string()), - accountUrl: schema.maybe(schema.string()), }); export type CloudConfigType = TypeOf; @@ -33,7 +32,6 @@ export const config: PluginConfigDescriptor = { id: true, resetPasswordUrl: true, deploymentUrl: true, - accountUrl: true, }, schema: configSchema, }; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index d0382c22ed3c67..8016c942240601 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -16,7 +16,6 @@ import { export { SecurityPluginSetup, SecurityPluginStart }; export { AuthenticatedUser } from '../common/model'; export { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; -export { UserMenuLink } from '../public/nav_control'; export const plugin: PluginInitializer< SecurityPluginSetup, diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts index 26a759ca522679..33c1d1446afba2 100644 --- a/x-pack/plugins/security/public/mocks.ts +++ b/x-pack/plugins/security/public/mocks.ts @@ -7,7 +7,6 @@ import { authenticationMock } from './authentication/index.mock'; import { createSessionTimeoutMock } from './session/session_timeout.mock'; import { licenseMock } from '../common/licensing/index.mock'; -import { navControlServiceMock } from './nav_control/index.mock'; function createSetupMock() { return { @@ -16,13 +15,7 @@ function createSetupMock() { license: licenseMock.create(), }; } -function createStartMock() { - return { - navControlService: navControlServiceMock.createStart(), - }; -} export const securityMock = { createSetup: createSetupMock, - createStart: createStartMock, }; diff --git a/x-pack/plugins/security/public/nav_control/index.mock.ts b/x-pack/plugins/security/public/nav_control/index.mock.ts deleted file mode 100644 index 1cd10810d7c8f1..00000000000000 --- a/x-pack/plugins/security/public/nav_control/index.mock.ts +++ /dev/null @@ -1,14 +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 { SecurityNavControlServiceStart } from '.'; - -export const navControlServiceMock = { - createStart: (): jest.Mocked => ({ - getUserMenuLinks$: jest.fn(), - addUserMenuLinks: jest.fn(), - }), -}; diff --git a/x-pack/plugins/security/public/nav_control/index.ts b/x-pack/plugins/security/public/nav_control/index.ts index 737ae500546987..2b0af1a45d05a9 100644 --- a/x-pack/plugins/security/public/nav_control/index.ts +++ b/x-pack/plugins/security/public/nav_control/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { SecurityNavControlService, SecurityNavControlServiceStart } from './nav_control_service'; -export { UserMenuLink } from './nav_control_component'; +export { SecurityNavControlService } from './nav_control_service'; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.scss b/x-pack/plugins/security/public/nav_control/nav_control_component.scss deleted file mode 100644 index a3e04b08cfac20..00000000000000 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.chrNavControl__userMenu { - .euiContextMenuPanelTitle { - // Uppercased by default, override to match actual username - text-transform: none; - } - - .euiContextMenuItem { - // Temp fix for EUI issue https://github.com/elastic/eui/issues/3092 - line-height: normal; - } -} \ No newline at end of file diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index 1da91e80d062de..c1c6a9f69b6ec9 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { BehaviorSubject } from 'rxjs'; import { shallowWithIntl, nextTick, mountWithIntl } from 'test_utils/enzyme_helpers'; import { SecurityNavControl } from './nav_control_component'; import { AuthenticatedUser } from '../../common/model'; @@ -18,7 +17,6 @@ describe('SecurityNavControl', () => { user: new Promise(() => {}) as Promise, editProfileUrl: '', logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), }; const wrapper = shallowWithIntl(); @@ -44,7 +42,6 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), }; const wrapper = shallowWithIntl(); @@ -73,7 +70,6 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), }; const wrapper = mountWithIntl(); @@ -95,7 +91,6 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), }; const wrapper = mountWithIntl(); @@ -112,37 +107,4 @@ describe('SecurityNavControl', () => { expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); }); - - it('renders a popover with additional user menu links registered by other plugins', async () => { - const props = { - user: Promise.resolve({ full_name: 'foo' }) as Promise, - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([ - { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, - { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, - { label: 'link3', href: 'path-to-link-3', iconType: 'empty', order: 3 }, - ]), - }; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(0); - expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(0); - expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); - - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(1); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); - expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(1); - expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(1); - expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(1); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); - }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index c22308fa8a43e0..3ddabb0dc55f8c 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -7,52 +7,38 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { Observable, Subscription } from 'rxjs'; + import { EuiAvatar, + EuiFlexGroup, + EuiFlexItem, EuiHeaderSectionItemButton, + EuiLink, + EuiText, + EuiSpacer, EuiPopover, EuiLoadingSpinner, - EuiIcon, - EuiContextMenu, - EuiContextMenuPanelItemDescriptor, - IconType, - EuiText, } from '@elastic/eui'; import { AuthenticatedUser } from '../../common/model'; -import './nav_control_component.scss'; - -export interface UserMenuLink { - label: string; - iconType: IconType; - href: string; - order?: number; -} - interface Props { user: Promise; editProfileUrl: string; logoutUrl: string; - userMenuLinks$: Observable; } interface State { isOpen: boolean; authenticatedUser: AuthenticatedUser | null; - userMenuLinks: UserMenuLink[]; } export class SecurityNavControl extends Component { - private subscription?: Subscription; - constructor(props: Props) { super(props); this.state = { isOpen: false, authenticatedUser: null, - userMenuLinks: [], }; props.user.then((authenticatedUser) => { @@ -62,18 +48,6 @@ export class SecurityNavControl extends Component { }); } - componentDidMount() { - this.subscription = this.props.userMenuLinks$.subscribe(async (userMenuLinks) => { - this.setState({ userMenuLinks }); - }); - } - - componentWillUnmount() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - onMenuButtonClick = () => { if (!this.state.authenticatedUser) { return; @@ -92,13 +66,13 @@ export class SecurityNavControl extends Component { render() { const { editProfileUrl, logoutUrl } = this.props; - const { authenticatedUser, userMenuLinks } = this.state; + const { authenticatedUser } = this.state; - const username = + const name = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; const buttonContents = authenticatedUser ? ( - + ) : ( ); @@ -118,60 +92,6 @@ export class SecurityNavControl extends Component { ); - const profileMenuItem = { - name: ( - - ), - icon: , - href: editProfileUrl, - 'data-test-subj': 'profileLink', - }; - - const logoutMenuItem = { - name: ( - - ), - icon: , - href: logoutUrl, - 'data-test-subj': 'logoutLink', - }; - - const items: EuiContextMenuPanelItemDescriptor[] = []; - - items.push(profileMenuItem); - - if (userMenuLinks.length) { - const userMenuLinkMenuItems = userMenuLinks - .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) - .map(({ label, iconType, href }: UserMenuLink) => ({ - name: {label}, - icon: , - href, - 'data-test-subj': `userMenuLink__${label}`, - })); - - items.push(...userMenuLinkMenuItems, { - isSeparator: true, - key: 'securityNavControlComponent__userMenuLinksSeparator', - }); - } - - items.push(logoutMenuItem); - - const panels = [ - { - id: 0, - title: username, - items, - }, - ]; - return ( { repositionOnScroll closePopover={this.closeMenu} panelPaddingSize="none" - buffer={0} > -
- +
+ + + + + + + +

{name}

+
+ + + + + + + + + + + + + + + + + + + + +
+
); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 4ae64d667ce293..aa3ec2e47469d0 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -4,16 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sortBy } from 'lodash'; -import { Observable, Subscription, BehaviorSubject, ReplaySubject } from 'rxjs'; -import { map, takeUntil } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; import { CoreStart } from 'src/core/public'; - import ReactDOM from 'react-dom'; import React from 'react'; - import { SecurityLicense } from '../../common/licensing'; -import { SecurityNavControl, UserMenuLink } from './nav_control_component'; +import { SecurityNavControl } from './nav_control_component'; import { AuthenticationServiceSetup } from '../authentication'; interface SetupDeps { @@ -26,18 +22,6 @@ interface StartDeps { core: CoreStart; } -export interface SecurityNavControlServiceStart { - /** - * Returns an Observable of the array of user menu links registered by other plugins - */ - getUserMenuLinks$: () => Observable; - - /** - * Registers the provided user menu links to be displayed in the user menu in the global nav - */ - addUserMenuLinks: (newUserMenuLink: UserMenuLink[]) => void; -} - export class SecurityNavControlService { private securityLicense!: SecurityLicense; private authc!: AuthenticationServiceSetup; @@ -47,16 +31,13 @@ export class SecurityNavControlService { private securityFeaturesSubscription?: Subscription; - private readonly stop$ = new ReplaySubject(1); - private userMenuLinks$ = new BehaviorSubject([]); - public setup({ securityLicense, authc, logoutUrl }: SetupDeps) { this.securityLicense = securityLicense; this.authc = authc; this.logoutUrl = logoutUrl; } - public start({ core }: StartDeps): SecurityNavControlServiceStart { + public start({ core }: StartDeps) { this.securityFeaturesSubscription = this.securityLicense.features$.subscribe( ({ showLinks }) => { const isAnonymousPath = core.http.anonymousPaths.isAnonymous(window.location.pathname); @@ -68,14 +49,6 @@ export class SecurityNavControlService { } } ); - - return { - getUserMenuLinks$: () => - this.userMenuLinks$.pipe(map(this.sortUserMenuLinks), takeUntil(this.stop$)), - addUserMenuLinks: (userMenuLink: UserMenuLink[]) => { - this.userMenuLinks$.next(userMenuLink); - }, - }; } public stop() { @@ -84,7 +57,6 @@ export class SecurityNavControlService { this.securityFeaturesSubscription = undefined; } this.navControlRegistered = false; - this.stop$.next(); } private registerSecurityNavControl( @@ -100,7 +72,6 @@ export class SecurityNavControlService { user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/security/account'), logoutUrl: this.logoutUrl, - userMenuLinks$: this.userMenuLinks$, }; ReactDOM.render( @@ -115,8 +86,4 @@ export class SecurityNavControlService { this.navControlRegistered = true; } - - private sortUserMenuLinks(userMenuLinks: UserMenuLink[]) { - return sortBy(userMenuLinks, 'order'); - } } diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index 6f5a2a031a7b22..d86d4812af5e3f 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -97,12 +97,7 @@ describe('Security Plugin', () => { data: {} as DataPublicPluginStart, features: {} as FeaturesPluginStart, }) - ).toEqual({ - navControlService: { - getUserMenuLinks$: expect.any(Function), - addUserMenuLinks: expect.any(Function), - }, - }); + ).toBeUndefined(); }); it('starts Management Service if `management` plugin is available', () => { diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index f94772c43dd896..700653c4cecb8e 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -146,13 +146,11 @@ export class SecurityPlugin public start(core: CoreStart, { management, securityOss }: PluginStartDependencies) { this.sessionTimeout.start(); + this.navControlService.start({ core }); this.securityCheckupService.start({ securityOssStart: securityOss, docLinks: core.docLinks }); - if (management) { this.managementService.start({ capabilities: core.application.capabilities }); } - - return { navControlService: this.navControlService.start({ core }) }; } public stop() { From 0faf8c24eec2906363c4538f6dd1060aba15b2d3 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 6 Nov 2020 11:34:57 +0300 Subject: [PATCH 16/81] Use monacco editor in the inspector request panel (#82272) * Use monacco editor in the inspector request panel Closes: #81921 * insRequestCodeViewer -> insRequestCodeViewer * remove uiSettings from props * fix functional tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/inspector/public/plugin.tsx | 11 +- .../inspector_panel.test.tsx.snap | 361 +++++++++--------- .../inspector/public/ui/inspector_panel.scss | 12 +- .../public/ui/inspector_panel.test.tsx | 10 +- .../inspector/public/ui/inspector_panel.tsx | 32 +- .../__snapshots__/data_view.test.tsx.snap | 243 ++---------- .../views/data/components/data_view.test.tsx | 14 +- .../views/data/components/data_view.tsx | 18 +- .../public/views/data/{index.tsx => index.ts} | 16 +- .../components/details/req_code_viewer.tsx | 82 ++++ .../details/req_details_request.tsx | 13 +- .../details/req_details_response.tsx | 13 +- .../requests/components/requests_view.tsx | 4 + .../inspector/public/views/requests/index.ts | 4 +- test/functional/page_objects/tile_map_page.ts | 4 +- test/functional/services/inspector.ts | 13 + .../maps/documents_source/docvalue_fields.js | 2 +- 17 files changed, 402 insertions(+), 450 deletions(-) rename src/plugins/inspector/public/views/data/{index.tsx => index.ts} (72%) create mode 100644 src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx diff --git a/src/plugins/inspector/public/plugin.tsx b/src/plugins/inspector/public/plugin.tsx index f906dbcab80439..07ef7c8fbab0d4 100644 --- a/src/plugins/inspector/public/plugin.tsx +++ b/src/plugins/inspector/public/plugin.tsx @@ -70,7 +70,7 @@ export class InspectorPublicPlugin implements Plugin { public async setup(core: CoreSetup) { this.views = new InspectorViewRegistry(); - this.views.register(getDataViewDescription(core.uiSettings)); + this.views.register(getDataViewDescription()); this.views.register(getRequestsViewDescription()); return { @@ -101,7 +101,14 @@ export class InspectorPublicPlugin implements Plugin { } return core.overlays.openFlyout( - toMountPoint(), + toMountPoint( + + ), { 'data-test-subj': 'inspectorPanel', closeButtonAriaLabel: closeButtonLabel, diff --git a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap index 709c0bfe69f0bd..7fb00fe8d40c41 100644 --- a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap +++ b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap @@ -10,6 +10,11 @@ exports[`InspectorPanel should render as expected 1`] = ` }, } } + dependencies={ + Object { + "uiSettings": Object {}, + } + } intl={ Object { "defaultFormats": Object {}, @@ -135,216 +140,228 @@ exports[`InspectorPanel should render as expected 1`] = ` ] } > - -
- -
- -
- -

- Inspector -

-
-
-
- -
+ Inspector +
+
+
+ + - + - - - + } + } + views={ + Array [ + Object { + "component": [Function], + "order": 200, + "title": "View 1", + }, + Object { + "component": [Function], + "order": 100, + "shouldShow": [Function], + "title": "Foo View", + }, + Object { + "component": [Function], + "order": 200, + "shouldShow": [Function], + "title": "Never", + }, + ] } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="inspectorViewChooser" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - repositionOnScroll={true} > - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="inspectorViewChooser" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + repositionOnScroll={true} > -
- - - + + + +
-
- -
- - - - - - - - -
+ + +
+ + + + + +
- -

- View 1 -

-
+ } + > + +

+ View 1 +

+
+
+
- -
+
+ `; diff --git a/src/plugins/inspector/public/ui/inspector_panel.scss b/src/plugins/inspector/public/ui/inspector_panel.scss index ff0b491e1222b9..2a6cfed66e4ff8 100644 --- a/src/plugins/inspector/public/ui/inspector_panel.scss +++ b/src/plugins/inspector/public/ui/inspector_panel.scss @@ -1,11 +1,15 @@ .insInspectorPanel__flyoutBody { - // TODO: EUI to allow for custom classNames to inner elements - // Or supply this as default - > div { + .euiFlyoutBody__overflowContent { + height: 100%; display: flex; + flex-wrap: nowrap; flex-direction: column; - > div { + >div { + flex-grow: 0; + } + + .insRequestCodeViewer { flex-grow: 1; } } diff --git a/src/plugins/inspector/public/ui/inspector_panel.test.tsx b/src/plugins/inspector/public/ui/inspector_panel.test.tsx index 23f698c23793b1..67e197abe7134e 100644 --- a/src/plugins/inspector/public/ui/inspector_panel.test.tsx +++ b/src/plugins/inspector/public/ui/inspector_panel.test.tsx @@ -22,10 +22,12 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { InspectorPanel } from './inspector_panel'; import { InspectorViewDescription } from '../types'; import { Adapters } from '../../common'; +import type { IUiSettingsClient } from 'kibana/public'; describe('InspectorPanel', () => { let adapters: Adapters; let views: InspectorViewDescription[]; + const uiSettings: IUiSettingsClient = {} as IUiSettingsClient; beforeEach(() => { adapters = { @@ -62,12 +64,16 @@ describe('InspectorPanel', () => { }); it('should render as expected', () => { - const component = mountWithIntl(); + const component = mountWithIntl( + + ); expect(component).toMatchSnapshot(); }); it('should not allow updating adapters', () => { - const component = mountWithIntl(); + const component = mountWithIntl( + + ); adapters.notAllowed = {}; expect(() => component.setProps({ adapters })).toThrow(); }); diff --git a/src/plugins/inspector/public/ui/inspector_panel.tsx b/src/plugins/inspector/public/ui/inspector_panel.tsx index 37a51257112d63..dbad202953b0b5 100644 --- a/src/plugins/inspector/public/ui/inspector_panel.tsx +++ b/src/plugins/inspector/public/ui/inspector_panel.tsx @@ -19,12 +19,21 @@ import './inspector_panel.scss'; import { i18n } from '@kbn/i18n'; -import React, { Component } from 'react'; +import React, { Component, Suspense } from 'react'; import PropTypes from 'prop-types'; -import { EuiFlexGroup, EuiFlexItem, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { IUiSettingsClient } from 'kibana/public'; import { InspectorViewDescription } from '../types'; import { Adapters } from '../../common'; import { InspectorViewChooser } from './inspector_view_chooser'; +import { KibanaContextProvider } from '../../../kibana_react/public'; function hasAdaptersChanged(oldAdapters: Adapters, newAdapters: Adapters) { return ( @@ -41,6 +50,9 @@ interface InspectorPanelProps { adapters: Adapters; title?: string; views: InspectorViewDescription[]; + dependencies: { + uiSettings: IUiSettingsClient; + }; } interface InspectorPanelState { @@ -95,19 +107,21 @@ export class InspectorPanel extends Component + }> + + ); } render() { - const { views, title } = this.props; + const { views, title, dependencies } = this.props; const { selectedView } = this.state; return ( - + @@ -127,7 +141,7 @@ export class InspectorPanel extends Component {this.renderSelectedPanel()} - + ); } } diff --git a/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap index 2632afff2f63be..3bd3bb6531cc7f 100644 --- a/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Inspector Data View component should render empty state 1`] = ` - - + `; exports[`Inspector Data View component should render loading state 1`] = ` - + loading + } intl={ Object { @@ -431,204 +439,9 @@ exports[`Inspector Data View component should render loading state 1`] = ` "timeZone": null, } } - title="Test Data" > - - -
- -
- -
- - - - - - - - - -
- - -
-

- - Gathering data - -

-
-
-
- -
- -
- - - +
+ loading +
+ `; diff --git a/src/plugins/inspector/public/views/data/components/data_view.test.tsx b/src/plugins/inspector/public/views/data/components/data_view.test.tsx index bd78bca42c4796..6a7f878ef807e8 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.test.tsx +++ b/src/plugins/inspector/public/views/data/components/data_view.test.tsx @@ -17,11 +17,10 @@ * under the License. */ -import React from 'react'; +import React, { Suspense } from 'react'; import { getDataViewDescription } from '../index'; import { DataAdapter } from '../../../../common/adapters/data'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { IUiSettingsClient } from '../../../../../../core/public'; jest.mock('../lib/export_csv', () => ({ exportAsCsv: jest.fn(), @@ -31,9 +30,7 @@ describe('Inspector Data View', () => { let DataView: any; beforeEach(() => { - const uiSettings = {} as IUiSettingsClient; - - DataView = getDataViewDescription(uiSettings); + DataView = getDataViewDescription(); }); it('should only show if data adapter is present', () => { @@ -51,7 +48,12 @@ describe('Inspector Data View', () => { }); it('should render loading state', () => { - const component = mountWithIntl(); // eslint-disable-line react/jsx-pascal-case + const DataViewComponent = DataView.component; + const component = mountWithIntl( + loading
}> + + + ); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/inspector/public/views/data/components/data_view.tsx b/src/plugins/inspector/public/views/data/components/data_view.tsx index 1a2b6f9922d2d0..100fa7787321ca 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.tsx +++ b/src/plugins/inspector/public/views/data/components/data_view.tsx @@ -38,6 +38,7 @@ import { TabularCallback, } from '../../../../common/adapters/data/types'; import { IUiSettingsClient } from '../../../../../../core/public'; +import { withKibana, KibanaReactContextValue } from '../../../../../kibana_react/public'; interface DataViewComponentState { tabularData: TabularData | null; @@ -47,20 +48,23 @@ interface DataViewComponentState { } interface DataViewComponentProps extends InspectorViewProps { - uiSettings: IUiSettingsClient; + kibana: KibanaReactContextValue<{ uiSettings: IUiSettingsClient }>; } -export class DataViewComponent extends Component { +class DataViewComponent extends Component { static propTypes = { - uiSettings: PropTypes.object.isRequired, adapters: PropTypes.object.isRequired, title: PropTypes.string.isRequired, + kibana: PropTypes.object, }; state = {} as DataViewComponentState; _isMounted = false; - static getDerivedStateFromProps(nextProps: InspectorViewProps, state: DataViewComponentState) { + static getDerivedStateFromProps( + nextProps: DataViewComponentProps, + state: DataViewComponentState + ) { if (state && nextProps.adapters === state.adapters) { return null; } @@ -172,8 +176,12 @@ export class DataViewComponent extends Component ); } } + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export default withKibana(DataViewComponent); diff --git a/src/plugins/inspector/public/views/data/index.tsx b/src/plugins/inspector/public/views/data/index.ts similarity index 72% rename from src/plugins/inspector/public/views/data/index.tsx rename to src/plugins/inspector/public/views/data/index.ts index b02e02bbe6b6b6..d201ad89022be9 100644 --- a/src/plugins/inspector/public/views/data/index.tsx +++ b/src/plugins/inspector/public/views/data/index.ts @@ -16,17 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { DataViewComponent } from './components/data_view'; -import { InspectorViewDescription, InspectorViewProps } from '../../types'; +import { InspectorViewDescription } from '../../types'; import { Adapters } from '../../../common'; -import { IUiSettingsClient } from '../../../../../core/public'; -export const getDataViewDescription = ( - uiSettings: IUiSettingsClient -): InspectorViewDescription => ({ +const DataViewComponent = lazy(() => import('./components/data_view')); + +export const getDataViewDescription = (): InspectorViewDescription => ({ title: i18n.translate('inspector.data.dataTitle', { defaultMessage: 'Data', }), @@ -37,7 +35,5 @@ export const getDataViewDescription = ( shouldShow(adapters: Adapters) { return Boolean(adapters.data); }, - component: (props: InspectorViewProps) => ( - - ), + component: DataViewComponent, }); diff --git a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx new file mode 100644 index 00000000000000..71499d46071c87 --- /dev/null +++ b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx @@ -0,0 +1,82 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiFlexItem, EuiFlexGroup, EuiCopy, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; + +import { CodeEditor } from '../../../../../../kibana_react/public'; + +interface RequestCodeViewerProps { + json: string; +} + +const copyToClipboardLabel = i18n.translate('inspector.requests.copyToClipboardLabel', { + defaultMessage: 'Copy to clipboard', +}); + +/** + * @internal + */ +export const RequestCodeViewer = ({ json }: RequestCodeViewerProps) => ( + + + +
+ + {(copy) => ( + + {copyToClipboardLabel} + + )} + +
+
+ + {}} + options={{ + readOnly: true, + lineNumbers: 'off', + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }} + /> + +
+); diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx index d7cb8f57456138..47ed226c24a5ce 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx @@ -19,9 +19,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { EuiCodeBlock } from '@elastic/eui'; import { Request } from '../../../../../common/adapters/request/types'; import { RequestDetailsProps } from '../types'; +import { RequestCodeViewer } from './req_code_viewer'; export class RequestDetailsRequest extends Component { static propTypes = { @@ -37,15 +37,6 @@ export class RequestDetailsRequest extends Component { return null; } - return ( - - {JSON.stringify(json, null, 2)} - - ); + return ; } } diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx index 933495ff473961..5ad5cc0537adaa 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx @@ -19,9 +19,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { EuiCodeBlock } from '@elastic/eui'; import { Request } from '../../../../../common/adapters/request/types'; import { RequestDetailsProps } from '../types'; +import { RequestCodeViewer } from './req_code_viewer'; export class RequestDetailsResponse extends Component { static propTypes = { @@ -40,15 +40,6 @@ export class RequestDetailsResponse extends Component { return null; } - return ( - - {JSON.stringify(responseJSON, null, 2)} - - ); + return ; } } diff --git a/src/plugins/inspector/public/views/requests/components/requests_view.tsx b/src/plugins/inspector/public/views/requests/components/requests_view.tsx index 13575de0c5064f..7762689daf4e68 100644 --- a/src/plugins/inspector/public/views/requests/components/requests_view.tsx +++ b/src/plugins/inspector/public/views/requests/components/requests_view.tsx @@ -175,3 +175,7 @@ export class RequestsViewComponent extends Component import('./components/requests_view')); + export const getRequestsViewDescription = (): InspectorViewDescription => ({ title: i18n.translate('inspector.requests.requestsTitle', { defaultMessage: 'Requests', diff --git a/test/functional/page_objects/tile_map_page.ts b/test/functional/page_objects/tile_map_page.ts index 609e6ebddd50ac..7881c9b1f7155c 100644 --- a/test/functional/page_objects/tile_map_page.ts +++ b/test/functional/page_objects/tile_map_page.ts @@ -50,12 +50,14 @@ export function TileMapPageProvider({ getService, getPageObjects }: FtrProviderC await testSubjects.click('inspectorViewChooser'); await testSubjects.click('inspectorViewChooserRequests'); await testSubjects.click('inspectorRequestDetailRequest'); - return await testSubjects.getVisibleText('inspectorRequestBody'); + + return await inspector.getCodeEditorValue(); } public async getMapBounds(): Promise { const request = await this.getVisualizationRequest(); const requestObject = JSON.parse(request); + return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates']; } diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index 1c0bf7ad46df15..e256cf14541a7e 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -23,6 +23,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function InspectorProvider({ getService }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); + const browser = getService('browser'); const renderable = getService('renderable'); const flyout = getService('flyout'); const testSubjects = getService('testSubjects'); @@ -245,6 +246,18 @@ export function InspectorProvider({ getService }: FtrProviderContext) { public getOpenRequestDetailResponseButton() { return testSubjects.find('inspectorRequestDetailResponse'); } + + public async getCodeEditorValue() { + let request: string = ''; + + await retry.try(async () => { + request = await browser.execute( + () => (window as any).monaco.editor.getModels()[0].getValue() as string + ); + }); + + return request; + } } return new Inspector(); diff --git a/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js b/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js index 4edee0a0b78ba8..a336ebc0d57dbb 100644 --- a/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js +++ b/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js @@ -26,7 +26,7 @@ export default function ({ getPageObjects, getService }) { await inspector.open(); await inspector.openInspectorRequestsView(); await testSubjects.click('inspectorRequestDetailResponse'); - const responseBody = await testSubjects.getVisibleText('inspectorResponseBody'); + const responseBody = await inspector.getCodeEditorValue(); await inspector.close(); return JSON.parse(responseBody); } From 1b65a674d0ee0478e7502262c7e74ee72afec585 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Fri, 6 Nov 2020 11:17:01 +0100 Subject: [PATCH 17/81] [Dashboard] Fix cloning panels reactive issue (#74253) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../actions/add_to_library_action.test.tsx | 21 +++----- .../actions/clone_panel_action.test.tsx | 7 ++- .../unlink_from_library_action.test.tsx | 20 ++----- .../embeddable/dashboard_container.test.tsx | 43 +++++++++++++++ .../embeddable/dashboard_container.tsx | 54 +++++++++---------- .../embeddable/grid/dashboard_grid.tsx | 3 ++ .../public/lib/containers/container.ts | 40 ++++++++++---- 7 files changed, 120 insertions(+), 68 deletions(-) diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx index 650a273314412d..feb30b248c066f 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx @@ -134,19 +134,15 @@ test('Add to library is not compatible when embeddable is not in a dashboard con expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false); }); -test('Add to library replaces embeddableId but retains panel count', async () => { +test('Add to library replaces embeddableId and retains panel count', async () => { const dashboard = embeddable.getRoot() as IContainer; const originalPanelCount = Object.keys(dashboard.getInput().panels).length; - const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); + const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts }); await action.execute({ embeddable }); expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount); - - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - expect(newPanelId).toBeDefined(); - const newPanel = container.getInput().panels[newPanelId!]; + expect(Object.keys(container.getInput().panels)).toContain(embeddable.id); + const newPanel = container.getInput().panels[embeddable.id!]; expect(newPanel.type).toEqual(embeddable.type); }); @@ -162,15 +158,10 @@ test('Add to library returns reference type input', async () => { mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: embeddable.id }, mockedByValueInput: { attributes: complicatedAttributes, id: embeddable.id } as EmbeddableInput, }); - const dashboard = embeddable.getRoot() as IContainer; - const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts }); await action.execute({ embeddable }); - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - expect(newPanelId).toBeDefined(); - const newPanel = container.getInput().panels[newPanelId!]; + expect(Object.keys(container.getInput().panels)).toContain(embeddable.id); + const newPanel = container.getInput().panels[embeddable.id!]; expect(newPanel.type).toEqual(embeddable.type); expect(newPanel.explicitInput.attributes).toBeUndefined(); expect(newPanel.explicitInput.savedObjectId).toBe('testSavedObjectId'); diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx index 193376ae97c0b0..25179fd7ccd387 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx @@ -108,7 +108,12 @@ test('Clone adds a new embeddable', async () => { ); expect(newPanelId).toBeDefined(); const newPanel = container.getInput().panels[newPanelId!]; - expect(newPanel.type).toEqual(embeddable.type); + expect(newPanel.type).toEqual('placeholder'); + // let the placeholder load + await dashboard.untilEmbeddableLoaded(newPanelId!); + // now wait for the full embeddable to replace it + const loadedPanel = await dashboard.untilEmbeddableLoaded(newPanelId!); + expect(loadedPanel.type).toEqual(embeddable.type); }); test('Clones an embeddable without a saved object ID', async () => { diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx index 4f668ec9ea04c9..f191be6f7baad3 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx @@ -132,19 +132,14 @@ test('Unlink is not compatible when embeddable is not in a dashboard container', expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false); }); -test('Unlink replaces embeddableId but retains panel count', async () => { +test('Unlink replaces embeddableId and retains panel count', async () => { const dashboard = embeddable.getRoot() as IContainer; const originalPanelCount = Object.keys(dashboard.getInput().panels).length; - const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); await action.execute({ embeddable }); expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount); - - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - expect(newPanelId).toBeDefined(); - const newPanel = container.getInput().panels[newPanelId!]; + expect(Object.keys(container.getInput().panels)).toContain(embeddable.id); + const newPanel = container.getInput().panels[embeddable.id!]; expect(newPanel.type).toEqual(embeddable.type); }); @@ -164,15 +159,10 @@ test('Unlink unwraps all attributes from savedObject', async () => { mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: embeddable.id }, mockedByValueInput: { attributes: complicatedAttributes, id: embeddable.id }, }); - const dashboard = embeddable.getRoot() as IContainer; - const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); await action.execute({ embeddable }); - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - expect(newPanelId).toBeDefined(); - const newPanel = container.getInput().panels[newPanelId!]; + expect(Object.keys(container.getInput().panels)).toContain(embeddable.id); + const newPanel = container.getInput().panels[embeddable.id!]; expect(newPanel.type).toEqual(embeddable.type); expect(newPanel.explicitInput.attributes).toEqual(complicatedAttributes); }); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 89aacf2a84029c..caa8321d7b8b23 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -27,6 +27,7 @@ import { ContactCardEmbeddableInput, ContactCardEmbeddable, ContactCardEmbeddableOutput, + EMPTY_EMBEDDABLE, } from '../../embeddable_plugin_test_samples'; import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; @@ -100,6 +101,48 @@ test('DashboardContainer.addNewEmbeddable', async () => { expect(embeddableInContainer.id).toBe(embeddable.id); }); +test('DashboardContainer.replacePanel', async (done) => { + const ID = '123'; + const initialInput = getSampleDashboardInput({ + panels: { + [ID]: getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: ID }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, + }); + + const container = new DashboardContainer(initialInput, options); + let counter = 0; + + const subscriptionHandler = jest.fn(({ panels }) => { + counter++; + expect(panels[ID]).toBeDefined(); + // It should be called exactly 2 times and exit the second time + switch (counter) { + case 1: + return expect(panels[ID].type).toBe(CONTACT_CARD_EMBEDDABLE); + + case 2: { + expect(panels[ID].type).toBe(EMPTY_EMBEDDABLE); + subscription.unsubscribe(); + done(); + } + + default: + throw Error('Called too many times!'); + } + }); + + const subscription = container.getInput$().subscribe(subscriptionHandler); + + // replace the panel now + container.replacePanel(container.getInput().panels[ID], { + type: EMPTY_EMBEDDABLE, + explicitInput: { id: ID }, + }); +}); + test('Container view mode change propagates to existing children', async () => { const initialInput = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 757488185fe8e5..051a7ef8bfb929 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -154,42 +154,43 @@ export class DashboardContainer extends Container) => - this.replacePanel(placeholderPanelState, newPanelState) - ); + + // wait until the placeholder is ready, then replace it with new panel + // this is useful as sometimes panels can load faster than the placeholder one (i.e. by value embeddables) + this.untilEmbeddableLoaded(originalPanelState.explicitInput.id) + .then(() => newStateComplete) + .then((newPanelState: Partial) => + this.replacePanel(placeholderPanelState, newPanelState) + ); } public replacePanel( previousPanelState: DashboardPanelState, newPanelState: Partial ) { - // TODO: In the current infrastructure, embeddables in a container do not react properly to - // changes. Removing the existing embeddable, and adding a new one is a temporary workaround - // until the container logic is fixed. - - const finalPanels = { ...this.input.panels }; - delete finalPanels[previousPanelState.explicitInput.id]; - const newPanelId = newPanelState.explicitInput?.id ? newPanelState.explicitInput.id : uuid.v4(); - finalPanels[newPanelId] = { - ...previousPanelState, - ...newPanelState, - gridData: { - ...previousPanelState.gridData, - i: newPanelId, - }, - explicitInput: { - ...newPanelState.explicitInput, - id: newPanelId, + // Because the embeddable type can change, we have to operate at the container level here + return this.updateInput({ + panels: { + ...this.input.panels, + [previousPanelState.explicitInput.id]: { + ...previousPanelState, + ...newPanelState, + gridData: { + ...previousPanelState.gridData, + }, + explicitInput: { + ...newPanelState.explicitInput, + id: previousPanelState.explicitInput.id, + }, + }, }, - }; - this.updateInput({ - panels: finalPanels, lastReloadRequestTime: new Date().getTime(), }); } @@ -201,16 +202,15 @@ export class DashboardContainer extends Container(type: string, explicitInput: Partial, embeddableId?: string) { const idToReplace = embeddableId || explicitInput.id; if (idToReplace && this.input.panels[idToReplace]) { - this.replacePanel(this.input.panels[idToReplace], { + return this.replacePanel(this.input.panels[idToReplace], { type, explicitInput: { ...explicitInput, - id: uuid.v4(), + id: idToReplace, }, }); - } else { - this.addNewEmbeddable(type, explicitInput); } + return this.addNewEmbeddable(type, explicitInput); } public render(dom: HTMLElement) { diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index d4d8fd0a4374b9..03c92d91a80ccb 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -265,6 +265,7 @@ class DashboardGridUi extends React.Component {
{ @@ -272,6 +273,8 @@ class DashboardGridUi extends React.Component { }} > this.maybeUpdateChildren()); + this.subscription = this.getInput$() + // At each update event, get both the previous and current state + .pipe(startWith(input), pairwise()) + .subscribe(([{ panels: prevPanels }, { panels: currentPanels }]) => { + this.maybeUpdateChildren(currentPanels, prevPanels); + }); } public updateInputForChild( @@ -329,16 +335,30 @@ export abstract class Container< return embeddable; } - private maybeUpdateChildren() { - const allIds = Object.keys({ ...this.input.panels, ...this.output.embeddableLoaded }); + private panelHasChanged(currentPanel: PanelState, prevPanel: PanelState) { + if (currentPanel.type !== prevPanel.type) { + return true; + } + } + + private maybeUpdateChildren( + currentPanels: TContainerInput['panels'], + prevPanels: TContainerInput['panels'] + ) { + const allIds = Object.keys({ ...currentPanels, ...this.output.embeddableLoaded }); allIds.forEach((id) => { - if (this.input.panels[id] !== undefined && this.output.embeddableLoaded[id] === undefined) { - this.onPanelAdded(this.input.panels[id]); - } else if ( - this.input.panels[id] === undefined && - this.output.embeddableLoaded[id] !== undefined - ) { - this.onPanelRemoved(id); + if (currentPanels[id] !== undefined && this.output.embeddableLoaded[id] === undefined) { + return this.onPanelAdded(currentPanels[id]); + } + if (currentPanels[id] === undefined && this.output.embeddableLoaded[id] !== undefined) { + return this.onPanelRemoved(id); + } + // In case of type change, remove and add a panel with the same id + if (currentPanels[id] && prevPanels[id]) { + if (this.panelHasChanged(currentPanels[id], prevPanels[id])) { + this.onPanelRemoved(id); + this.onPanelAdded(currentPanels[id]); + } } }); } From 814603455937e7cf4d9c9de0aedae0c8dabbcef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Fri, 6 Nov 2020 11:18:42 +0100 Subject: [PATCH 18/81] [Security Solution] Bump why-did-you-render (#82591) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 44a0c833eae278..1c218307b35c39 100644 --- a/package.json +++ b/package.json @@ -567,7 +567,7 @@ "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^3.10.0", "@typescript-eslint/parser": "^3.10.0", - "@welldone-software/why-did-you-render": "^4.0.0", + "@welldone-software/why-did-you-render": "^5.0.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^1.0.4", "angular-aria": "^1.8.0", diff --git a/yarn.lock b/yarn.lock index 6ba53d0e4dd43e..b79e246b27851b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6242,10 +6242,10 @@ text-table "^0.2.0" webpack-log "^1.1.2" -"@welldone-software/why-did-you-render@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-4.0.0.tgz#cc98c996f5a06ea55bd07dc99ba4b4d68af93332" - integrity sha512-PjqriZ8Ak9biP2+kOcIrg+NwsFwWVhGV03Hm+ns84YBCArn+hWBKM9rMBEU6e62I1qyrYF2/G9yktNpEmfWfJA== +"@welldone-software/why-did-you-render@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-5.0.0.tgz#5dd8d20ad9f00fd500de852dd06eea0c057a0bce" + integrity sha512-A6xUP/55vJQwA1+L6iZbG81cQanSQQVR15yPcjLIp6lHmybXEOXsYcuXaDZHYqiNStZRzv64YPcYJC9wdphfhw== dependencies: lodash "^4" From b0eb2779838e339cc8c1d6a3562abf4299ad09eb Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Fri, 6 Nov 2020 11:57:12 +0100 Subject: [PATCH 19/81] Add steps to migrate from a legacy kibana index (#82161) * Add steps to migrate from a legacy kibana index * Clarify data loss from legacy index to alias with same name * Use aliases api to safely add a .kibana alias to a legacy index --- rfcs/text/0013_saved_object_migrations.md | 53 ++++++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/rfcs/text/0013_saved_object_migrations.md b/rfcs/text/0013_saved_object_migrations.md index c5069625cb8a66..1a0967d110d06a 100644 --- a/rfcs/text/0013_saved_object_migrations.md +++ b/rfcs/text/0013_saved_object_migrations.md @@ -212,39 +212,68 @@ Note: If none of the aliases exists, this is a new Elasticsearch cluster and no migrations are necessary. Create the `.kibana_7.10.0_001` index with the following aliases: `.kibana_current` and `.kibana_7.10.0`. -2. If `.kibana_current` and `.kibana_7.10.0` both exists and are pointing to the same index this version's migration has already been completed. +2. If the source is a < v6.5 `.kibana` index or < 7.4 `.kibana_task_manager` + index prepare the legacy index for a migration: + 1. Mark the legacy index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds. + 2. Clone the legacy index into a new index which has writes enabled. Use a fixed index name i.e `.kibana_pre6.5.0_001` or `.kibana_task_manager_pre7.4.0_001`. `POST /.kibana/_clone/.kibana_pre6.5.0_001?wait_for_active_shards=all {"settings": {"index.blocks.write": false}}`. Ignore errors if the clone already exists. Ignore errors if the legacy source doesn't exist. + 3. Wait for the cloning to complete `GET /_cluster/health/.kibana_pre6.5.0_001?wait_for_status=green&timeout=60s` If cloning doesn’t complete within the 60s timeout, log a warning for visibility and poll again. + 4. Apply the `convertToAlias` script if defined `POST /.kibana_pre6.5.0_001/_update_by_query?conflicts=proceed {"script": {...}}`. The `convertToAlias` script will have to be idempotent, preferably setting `ctx.op="noop"` on subsequent runs to avoid unecessary writes. + 5. Delete the legacy index and replace it with an alias of the same name + ``` + POST /_aliases + { + "actions" : [ + { "add": { "index": ".kibana_pre6.5.0_001", "alias": ".kibana" } }, + { "remove_index": { "index": ".kibana" } } + ] + } + ```. + Unlike the delete index API, the `remove_index` action will fail if + provided with an _alias_. Ignore "The provided expression [.kibana] + matches an alias, specify the corresponding concrete indices instead." + or "index_not_found_exception" errors. These actions are applied + atomically so that other Kibana instances will always see either a + `.kibana` index or an alias, but never neither. + 6. Use the cloned `.kibana_pre6.5.0_001` as the source for the rest of the migration algorithm. +3. If `.kibana_current` and `.kibana_7.10.0` both exists and are pointing to the same index this version's migration has already been completed. 1. Because the same version can have plugins enabled at any point in time, perform the mappings update in step (6) and migrate outdated documents with step (7). 2. Skip to step (9) to start serving traffic. -3. Fail the migration if: +4. Fail the migration if: 1. `.kibana_current` is pointing to an index that belongs to a later version of Kibana .e.g. `.kibana_7.12.0_001` 2. (Only in 8.x) The source index contains documents that belong to an unknown Saved Object type (from a disabled plugin). Log an error explaining that the plugin that created these documents needs to be enabled again or that these objects should be deleted. See section (4.2.1.4). -4. Mark the source index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds. -5. Clone the source index into a new target index which has writes enabled. All nodes on the same version will use the same fixed index name e.g. `.kibana_7.10.0_001`. The `001` postfix isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`. +5. Mark the source index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds. +6. Clone the source index into a new target index which has writes enabled. All nodes on the same version will use the same fixed index name e.g. `.kibana_7.10.0_001`. The `001` postfix isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`. 1. `POST /.kibana_n/_clone/.kibana_7.10.0_001?wait_for_active_shards=all {"settings": {"index.blocks.write": false}}`. Ignore errors if the clone already exists. 2. Wait for the cloning to complete `GET /_cluster/health/.kibana_7.10.0_001?wait_for_status=green&timeout=60s` If cloning doesn’t complete within the 60s timeout, log a warning for visibility and poll again. -6. Update the mappings of the target index +7. Update the mappings of the target index 1. Retrieve the existing mappings including the `migrationMappingPropertyHashes` metadata. 2. Update the mappings with `PUT /.kibana_7.10.0_001/_mapping`. The API deeply merges any updates so this won't remove the mappings of any plugins that were enabled in a previous version but are now disabled. 3. Ensure that fields are correctly indexed using the target index's latest mappings `POST /.kibana_7.10.0_001/_update_by_query?conflicts=proceed`. In the future we could optimize this query by only targeting documents: 1. That belong to a known saved object type. 2. Which don't have outdated migrationVersion numbers since these will be transformed anyway. 3. That belong to a type whose mappings were changed by comparing the `migrationMappingPropertyHashes`. (Metadata, unlike the mappings isn't commutative, so there is a small chance that the metadata hashes do not accurately reflect the latest mappings, however, this will just result in an less efficient query). -7. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control. +8. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control. 1. Ignore any version conflict errors. 2. If a document transform throws an exception, add the document to a failure list and continue trying to transform all other documents. If any failures occured, log the complete list of documents that failed to transform. Fail the migration. -8. Mark the migration as complete by doing a single atomic operation (requires https://github.com/elastic/elasticsearch/pull/58100) that: - 1. Checks that `.kibana-current` alias is still pointing to the source index - 2. Points the `.kibana-7.10.0` and `.kibana_current` aliases to the target index. - 3. If this fails with a "required alias [.kibana_current] does not exist" error fetch `.kibana_current` again: +9. Mark the migration as complete by doing a single atomic operation (requires https://github.com/elastic/elasticsearch/pull/58100) that: + 3. Checks that `.kibana_current` alias is still pointing to the source index + 4. Points the `.kibana_7.10.0` and `.kibana_current` aliases to the target index. + 5. If this fails with a "required alias [.kibana_current] does not exist" error fetch `.kibana_current` again: 1. If `.kibana_current` is _not_ pointing to our target index fail the migration. 2. If `.kibana_current` is pointing to our target index the migration has succeeded and we can proceed to step (9). -9. Start serving traffic. +10. Start serving traffic. + +This algorithm shares a weakness with our existing migration algorithm +(since v7.4). When the task manager index gets reindexed a reindex script is +applied. Because we delete the original task manager index there is no way to +rollback a failed task manager migration without a snapshot. Together with the limitations, this algorithm ensures that migrations are idempotent. If two nodes are started simultaneously, both of them will start -transforming documents in that version's target index, but because migrations are idempotent, it doesn’t matter which node’s writes win. +transforming documents in that version's target index, but because migrations +are idempotent, it doesn’t matter which node’s writes win.
In the future, this algorithm could enable (2.6) "read-only functionality during the downtime window" but this is outside of the scope of this RFC. From d83167629c60a4263e1479591a9188adc25e76b0 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 6 Nov 2020 12:18:54 +0100 Subject: [PATCH 20/81] fix underlying data drilldown for Lens (#82737) --- .../embeddable/embeddable.test.tsx | 38 +++++++++++++++++++ .../embeddable/embeddable.tsx | 6 ++- x-pack/test/functional/apps/lens/dashboard.ts | 35 ++++++++++++++++- 3 files changed, 75 insertions(+), 4 deletions(-) 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 5658f029c48ab8..9f9d7fef9c7b4f 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 @@ -139,6 +139,44 @@ describe('embeddable', () => { | expression`); }); + it('should initialize output with deduped list of index patterns', async () => { + attributeService = attributeServiceMockFromSavedVis({ + ...savedVis, + references: [ + { type: 'index-pattern', id: '123', name: 'abc' }, + { type: 'index-pattern', id: '123', name: 'def' }, + { type: 'index-pattern', id: '456', name: 'ghi' }, + ], + }); + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: ({ + get: (id: string) => Promise.resolve({ id }), + } as unknown) as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }), + }, + {} as LensEmbeddableInput + ); + await embeddable.initializeSavedVis({} as LensEmbeddableInput); + const outputIndexPatterns = embeddable.getOutput().indexPatterns!; + expect(outputIndexPatterns.length).toEqual(2); + expect(outputIndexPatterns[0].id).toEqual('123'); + expect(outputIndexPatterns[1].id).toEqual('456'); + }); + it('should re-render if new input is pushed', 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 fdb267835f44c9..33e5dee99081ff 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 @@ -259,8 +259,10 @@ export class Embeddable if (!this.savedVis) { return; } - const promises = this.savedVis.references - .filter(({ type }) => type === 'index-pattern') + const promises = _.uniqBy( + this.savedVis.references.filter(({ type }) => type === 'index-pattern'), + 'id' + ) .map(async ({ id }) => { try { return await this.deps.indexPatternService.get(id); diff --git a/x-pack/test/functional/apps/lens/dashboard.ts b/x-pack/test/functional/apps/lens/dashboard.ts index fa13d013ea1157..c24f4ccf01bcd5 100644 --- a/x-pack/test/functional/apps/lens/dashboard.ts +++ b/x-pack/test/functional/apps/lens/dashboard.ts @@ -8,7 +8,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['header', 'common', 'dashboard', 'timePicker', 'lens']); + const PageObjects = getPageObjects([ + 'header', + 'common', + 'dashboard', + 'timePicker', + 'lens', + 'discover', + ]); const find = getService('find'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -18,6 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); const security = getService('security'); + const panelActions = getService('dashboardPanelActions'); async function clickInChart(x: number, y: number) { const el = await elasticChart.getCanvas(); @@ -27,7 +35,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('lens dashboard tests', () => { before(async () => { await PageObjects.common.navigateToApp('dashboard'); - await security.testUser.setRoles(['global_dashboard_all', 'test_logstash_reader'], false); + await security.testUser.setRoles( + ['global_dashboard_all', 'global_discover_all', 'test_logstash_reader'], + false + ); }); after(async () => { await security.testUser.restoreDefaults(); @@ -68,6 +79,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hasIpFilter = await filterBar.hasFilter('ip', '97.220.3.248'); expect(hasIpFilter).to.be(true); }); + + it('should be able to drill down to discover', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); + await find.clickByButtonText('lnsXYvis'); + await dashboardAddPanel.closeAddPanel(); + await PageObjects.lens.goToTimeRange(); + await PageObjects.dashboard.saveDashboard('lnsDrilldown'); + await panelActions.openContextMenu(); + await testSubjects.clickWhenNotDisabled('embeddablePanelAction-ACTION_EXPLORE_DATA'); + await PageObjects.discover.waitForDiscoverAppOnScreen(); + + const el = await testSubjects.find('indexPattern-switch-link'); + const text = await el.getVisibleText(); + + expect(text).to.be('logstash-*'); + }); + it('should be able to add filters by clicking in pie chart', async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); From 8d5ee265b455df3a090e4a49af106d87f0513771 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 6 Nov 2020 12:51:38 +0100 Subject: [PATCH 21/81] [Uptime] Display response headers for a ping (#82332) --- .../uptime/common/runtime_types/ping/ping.ts | 1 + .../__snapshots__/ping_headers.test.tsx.snap | 94 +++++++++++++++++++ .../ping_list/__tests__/ping_headers.test.tsx | 31 ++++++ .../monitor/ping_list/expanded_row.tsx | 6 ++ .../components/monitor/ping_list/headers.tsx | 48 ++++++++++ 5 files changed, 180 insertions(+) create mode 100644 x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap create mode 100644 x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/ping_list/headers.tsx 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 775078a7e5df16..315b8f543b800e 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -145,6 +145,7 @@ export const PingType = t.intersection([ bytes: t.number, redirects: t.array(t.string), status_code: t.number, + headers: t.record(t.string, t.string), }), version: t.string, }), diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap new file mode 100644 index 00000000000000..ef707dc3ea3153 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Ping Headers shallow renders expected elements for valid props 1`] = ` + + + +

+ Response headers +

+ + } + id="responseHeaderAccord" + initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} + paddingSize="none" + > + + +
+
+`; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx new file mode 100644 index 00000000000000..86394a0a4d8414 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; +import { PingHeaders } from '../headers'; + +describe('Ping Headers', () => { + const headers = { + 'Content-Type': 'text/html', + 'Content-Length': '174781', + Expires: 'Mon, 02 Nov 2020 17:22:03 GMT', + 'X-Xss-Protection': '0', + 'Accept-Ranges': 'bytes', + Date: 'Mon, 02 Nov 2020 17:22:03 GMT', + 'Cache-Control': 'private, max-age=0', + 'Alt-Svc': + 'h3-Q050=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"', + Server: 'sffe', + 'Last-Modified': 'Wed, 28 Oct 2020 18:45:00 GMT', + Vary: 'Accept-Encoding', + 'X-Content-Type-Options': 'nosniff', + }; + + it('shallow renders expected elements for valid props', () => { + expect(shallowWithIntl()).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx index 6af38eca6b0e97..e6a9b1ebe9c841 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx @@ -21,6 +21,7 @@ import { Ping, HttpResponseBody } from '../../../../common/runtime_types'; import { DocLinkForBody } from './doc_link_body'; import { PingRedirects } from './ping_redirects'; import { BrowserExpandedRow } from '../synthetics/browser_expanded_row'; +import { PingHeaders } from './headers'; interface Props { ping: Ping; @@ -105,6 +106,11 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => { )} + {ping?.http?.response?.headers && ( + + + + )} diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/headers.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/headers.tsx new file mode 100644 index 00000000000000..52fe26a7e08ca0 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/headers.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 from 'react'; +import { EuiAccordion, EuiDescriptionList, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + headers: Record; +} + +export const PingHeaders = ({ headers }: Props) => { + const headersList = Object.keys(headers) + .sort() + .map((header) => ({ + title: header, + description: headers[header], + })); + + return ( + <> + + +

+ {i18n.translate('xpack.uptime.pingList.headers.title', { + defaultMessage: 'Response headers', + })} +

+ + } + > + + +
+ + ); +}; From 3da6efcc7320a2f7b213a1cc2dc27491e9e04b1a Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 6 Nov 2020 07:06:11 -0500 Subject: [PATCH 22/81] Bump trim to 0.0.3 (#82800) --- package.json | 1 + yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1c218307b35c39..322bdf445758ac 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "**/minimist": "^1.2.5", "**/node-jose/node-forge": "^0.10.0", "**/request": "^2.88.2", + "**/trim": "0.0.3", "**/typescript": "4.0.2" }, "engines": { diff --git a/yarn.lock b/yarn.lock index b79e246b27851b..6e6647016dac2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28431,10 +28431,10 @@ trim-trailing-lines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz#7aefbb7808df9d669f6da2e438cac8c46ada7684" integrity sha1-eu+7eAjfnWafbaLkOMrIxGradoQ= -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= +trim@0.0.1, trim@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.3.tgz#05243a47a3a4113e6b49367880a9cca59697a20b" + integrity sha512-h82ywcYhHK7veeelXrCScdH7HkWfbIT1D/CgYO+nmDarz3SGNssVBMws6jU16Ga60AJCRAvPV6w6RLuNerQqjg== triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" From 1046fc299cfe90c272da379725d79ef56c31f9ab Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Fri, 6 Nov 2020 13:16:03 +0100 Subject: [PATCH 23/81] [Ingest Manager] Add tests to verify field parsing behavior. (#82809) * Add tests to verify field parsing behavior. * Verify behavior for multiple field redefinitions. --- .../server/services/epm/fields/field.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts index abd2ba777e516b..dcd8846fa96a45 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts @@ -547,4 +547,77 @@ describe('processFields', () => { ]; expect(processFields(nested)).toEqual(nestedExpected); }); + + test('ignores redefinitions of a field', () => { + const fields = [ + { + name: 'a', + type: 'text', + }, + { + name: 'a', + type: 'number', + }, + { + name: 'b.c', + type: 'number', + }, + { + name: 'b', + type: 'group', + fields: [ + { + name: 'c', + type: 'text', + }, + ], + }, + ]; + + const fieldsExpected = [ + { + name: 'a', + // should preserve the field that was parsed first which had type: text + type: 'text', + }, + { + name: 'b', + type: 'group', + fields: [ + { + name: 'c', + // should preserve the field that was parsed first which had type: number + type: 'number', + }, + ], + }, + ]; + expect(processFields(fields)).toEqual(fieldsExpected); + }); + + test('ignores multiple redefinitions of a field', () => { + const fields = [ + { + name: 'a', + type: 'text', + }, + { + name: 'a', + type: 'number', + }, + { + name: 'a', + type: 'keyword', + }, + ]; + + const fieldsExpected = [ + { + name: 'a', + // should preserve the field that was parsed first which had type: text + type: 'text', + }, + ]; + expect(processFields(fields)).toEqual(fieldsExpected); + }); }); From dae28519e654c3021b6795010a2a54289c9af2df Mon Sep 17 00:00:00 2001 From: ymao1 Date: Fri, 6 Nov 2020 07:28:08 -0500 Subject: [PATCH 24/81] [Alerting] Display Action Group in Alert Details (#82645) * Adding action group id to event log. Showing action group as part of status in alert details view * Simplifying getting action group id * Cleanup * Adding unit tests * Updating functional tests * Updating test * Fix types check * Updating test * PR fixes * PR fixes --- .../alerts/common/alert_instance_summary.ts | 1 + .../tests/get_alert_instance_summary.test.ts | 9 +- ...rt_instance_summary_from_event_log.test.ts | 117 +++++++++++++++--- .../alert_instance_summary_from_event_log.ts | 3 + .../server/task_runner/task_runner.test.ts | 11 +- .../alerts/server/task_runner/task_runner.ts | 18 +-- .../plugins/event_log/generated/mappings.json | 4 + x-pack/plugins/event_log/generated/schemas.ts | 1 + x-pack/plugins/event_log/scripts/mappings.js | 5 + .../components/alert_details.tsx | 1 + .../components/alert_instances.test.tsx | 96 ++++++++++---- .../components/alert_instances.tsx | 38 +++++- .../components/alert_instances_route.test.tsx | 22 +++- .../components/alert_instances_route.tsx | 5 +- .../spaces_only/tests/alerting/event_log.ts | 2 +- .../alerting/get_alert_instance_summary.ts | 2 + .../apps/triggers_actions_ui/details.ts | 6 +- 17 files changed, 276 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/alerts/common/alert_instance_summary.ts b/x-pack/plugins/alerts/common/alert_instance_summary.ts index 08c3b2fc2c241c..1aa183a141eab6 100644 --- a/x-pack/plugins/alerts/common/alert_instance_summary.ts +++ b/x-pack/plugins/alerts/common/alert_instance_summary.ts @@ -27,5 +27,6 @@ export interface AlertInstanceSummary { export interface AlertInstanceStatus { status: AlertInstanceStatusValues; muted: boolean; + actionGroupId?: string; activeStartDate?: string; } diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index a53e49337f3857..9cb2a33222d23d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -118,12 +118,12 @@ describe('getAlertInstanceSummary()', () => { .addExecute() .addNewInstance('instance-currently-active') .addNewInstance('instance-previously-active') - .addActiveInstance('instance-currently-active') - .addActiveInstance('instance-previously-active') + .addActiveInstance('instance-currently-active', 'action group A') + .addActiveInstance('instance-previously-active', 'action group B') .advanceTime(10000) .addExecute() .addResolvedInstance('instance-previously-active') - .addActiveInstance('instance-currently-active') + .addActiveInstance('instance-currently-active', 'action group A') .getEvents(); const eventsResult = { ...AlertInstanceSummaryFindEventsResult, @@ -144,16 +144,19 @@ describe('getAlertInstanceSummary()', () => { "id": "1", "instances": Object { "instance-currently-active": Object { + "actionGroupId": "action group A", "activeStartDate": "2019-02-12T21:01:22.479Z", "muted": false, "status": "Active", }, "instance-muted-no-activity": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", }, "instance-previously-active": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index 566a1770c0658e..f9e4a2908d6ce3 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -104,11 +104,13 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", }, "instance-2": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", @@ -184,7 +186,7 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() .addResolvedInstance('instance-1') @@ -202,6 +204,7 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -218,7 +221,7 @@ describe('alertInstanceSummaryFromEventLog', () => { const eventsFactory = new EventsFactory(); const events = eventsFactory .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() .addResolvedInstance('instance-1') @@ -236,6 +239,7 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -253,10 +257,10 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -271,6 +275,79 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group A", + "activeStartDate": "2020-06-18T00:00:00.000Z", + "muted": false, + "status": "Active", + }, + }, + "lastRun": "2020-06-18T00:00:10.000Z", + "status": "Active", + } + `); + }); + + test('alert with currently active instance with no action group in event log', async () => { + const alert = createAlert({}); + const eventsFactory = new EventsFactory(); + const events = eventsFactory + .addExecute() + .addNewInstance('instance-1') + .addActiveInstance('instance-1', undefined) + .advanceTime(10000) + .addExecute() + .addActiveInstance('instance-1', undefined) + .getEvents(); + + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); + + const { lastRun, status, instances } = summary; + expect({ lastRun, status, instances }).toMatchInlineSnapshot(` + Object { + "instances": Object { + "instance-1": Object { + "actionGroupId": undefined, + "activeStartDate": "2020-06-18T00:00:00.000Z", + "muted": false, + "status": "Active", + }, + }, + "lastRun": "2020-06-18T00:00:10.000Z", + "status": "Active", + } + `); + }); + + test('alert with currently active instance that switched action groups', async () => { + const alert = createAlert({}); + const eventsFactory = new EventsFactory(); + const events = eventsFactory + .addExecute() + .addNewInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') + .advanceTime(10000) + .addExecute() + .addActiveInstance('instance-1', 'action group B') + .getEvents(); + + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); + + const { lastRun, status, instances } = summary; + expect({ lastRun, status, instances }).toMatchInlineSnapshot(` + Object { + "instances": Object { + "instance-1": Object { + "actionGroupId": "action group B", "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", @@ -287,10 +364,10 @@ describe('alertInstanceSummaryFromEventLog', () => { const eventsFactory = new EventsFactory(); const events = eventsFactory .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -305,6 +382,7 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group A", "activeStartDate": undefined, "muted": false, "status": "Active", @@ -322,12 +400,12 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addNewInstance('instance-2') - .addActiveInstance('instance-2') + .addActiveInstance('instance-2', 'action group B') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addResolvedInstance('instance-2') .getEvents(); @@ -343,11 +421,13 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group A", "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": true, "status": "Active", }, "instance-2": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", @@ -365,19 +445,19 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addNewInstance('instance-2') - .addActiveInstance('instance-2') + .addActiveInstance('instance-2', 'action group B') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addResolvedInstance('instance-2') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group B') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group B') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -392,11 +472,13 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group B", "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", }, "instance-2": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -452,14 +534,17 @@ export class EventsFactory { return this; } - addActiveInstance(instanceId: string): EventsFactory { + addActiveInstance(instanceId: string, actionGroupId: string | undefined): EventsFactory { + const kibanaAlerting = actionGroupId + ? { instance_id: instanceId, action_group_id: actionGroupId } + : { instance_id: instanceId }; this.events.push({ '@timestamp': this.date, event: { provider: EVENT_LOG_PROVIDER, action: EVENT_LOG_ACTIONS.activeInstance, }, - kibana: { alerting: { instance_id: instanceId } }, + kibana: { alerting: kibanaAlerting }, }); return this; } diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index 9a5e870c8199a2..8fed97a74435dc 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -78,10 +78,12 @@ export function alertInstanceSummaryFromEventLog( // intentionally no break here case EVENT_LOG_ACTIONS.activeInstance: status.status = 'Active'; + status.actionGroupId = event?.kibana?.alerting?.action_group_id; break; case EVENT_LOG_ACTIONS.resolvedInstance: status.status = 'OK'; status.activeStartDate = undefined; + status.actionGroupId = undefined; } } @@ -118,6 +120,7 @@ function getAlertInstanceStatus( const status: AlertInstanceStatus = { status: 'OK', muted: false, + actionGroupId: undefined, activeStartDate: undefined, }; instances.set(instanceId, status); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 86e78dea66a095..4d0d69010914e2 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -292,6 +292,7 @@ describe('Task Runner', () => { kibana: { alerting: { instance_id: '1', + action_group_id: 'default', }, saved_objects: [ { @@ -302,7 +303,7 @@ describe('Task Runner', () => { }, ], }, - message: "test:1: 'alert-name' active instance: '1'", + message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }); expect(eventLogger.logEvent).toHaveBeenCalledWith({ event: { @@ -424,6 +425,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": undefined, "instance_id": "1", }, "saved_objects": Array [ @@ -445,6 +447,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": "default", "instance_id": "1", }, "saved_objects": Array [ @@ -456,7 +459,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' active instance: '1'", + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }, ], Array [ @@ -565,6 +568,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": undefined, "instance_id": "2", }, "saved_objects": Array [ @@ -586,6 +590,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": "default", "instance_id": "1", }, "saved_objects": Array [ @@ -597,7 +602,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' active instance: '1'", + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }, ], ] diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 2611ba766173ba..6a49f67268d697 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { pickBy, mapValues, without } from 'lodash'; +import { Dictionary, pickBy, mapValues, without } from 'lodash'; import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance, throwUnrecoverableError } from '../../../task_manager/server'; @@ -224,11 +224,10 @@ export class TaskRunner { const instancesWithScheduledActions = pickBy(alertInstances, (alertInstance: AlertInstance) => alertInstance.hasScheduledActions() ); - const currentAlertInstanceIds = Object.keys(instancesWithScheduledActions); generateNewAndResolvedInstanceEvents({ eventLogger, originalAlertInstanceIds, - currentAlertInstanceIds, + currentAlertInstances: instancesWithScheduledActions, alertId, alertLabel, namespace, @@ -382,7 +381,7 @@ export class TaskRunner { interface GenerateNewAndResolvedInstanceEventsParams { eventLogger: IEventLogger; originalAlertInstanceIds: string[]; - currentAlertInstanceIds: string[]; + currentAlertInstances: Dictionary; alertId: string; alertLabel: string; namespace: string | undefined; @@ -393,9 +392,10 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst eventLogger, alertId, namespace, - currentAlertInstanceIds, + currentAlertInstances, originalAlertInstanceIds, } = params; + const currentAlertInstanceIds = Object.keys(currentAlertInstances); const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); @@ -411,11 +411,12 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst } for (const id of currentAlertInstanceIds) { - const message = `${params.alertLabel} active instance: '${id}'`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message); + const actionGroup = currentAlertInstances[id].getScheduledActionOptions()?.actionGroup; + const message = `${params.alertLabel} active instance: '${id}' in actionGroup: '${actionGroup}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message, actionGroup); } - function logInstanceEvent(instanceId: string, action: string, message: string) { + function logInstanceEvent(instanceId: string, action: string, message: string, group?: string) { const event: IEvent = { event: { action, @@ -423,6 +424,7 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst kibana: { alerting: { instance_id: instanceId, + action_group_id: group, }, saved_objects: [ { diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index 0a858969c4f6af..5c7eb50117d9b1 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -81,6 +81,10 @@ "instance_id": { "type": "keyword", "ignore_above": 1024 + }, + "action_group_id": { + "type": "keyword", + "ignore_above": 1024 } } }, diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index 57fe90a8e876ed..3dbb43b15350f6 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -60,6 +60,7 @@ export const EventSchema = schema.maybe( alerting: schema.maybe( schema.object({ instance_id: ecsString(), + action_group_id: ecsString(), }) ), saved_objects: schema.maybe( diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index fd149d132031e1..c9af2b0aa57fb4 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -18,6 +18,10 @@ exports.EcsKibanaExtensionsMappings = { type: 'keyword', ignore_above: 1024, }, + action_group_id: { + type: 'keyword', + ignore_above: 1024, + }, }, }, // array of saved object references, for "linking" via search @@ -63,6 +67,7 @@ exports.EcsEventLogProperties = [ 'user.name', 'kibana.server_uuid', 'kibana.alerting.instance_id', + 'kibana.alerting.action_group_id', 'kibana.saved_objects.rel', 'kibana.saved_objects.namespace', 'kibana.saved_objects.id', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 1272024557bb65..abd81279625618 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -301,6 +301,7 @@ export const AlertDetails: React.FunctionComponent = ({ ) : ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index e1287d299b6e92..25bbe977fd76ad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; import { AlertInstances, AlertInstanceListItem, alertInstanceToListItem } from './alert_instances'; -import { Alert, AlertInstanceSummary, AlertInstanceStatus } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertInstanceStatus, AlertType } from '../../../../types'; import { EuiBasicTable } from '@elastic/eui'; const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -34,15 +34,18 @@ jest.mock('../../../app_context', () => { describe('alert_instances', () => { it('render a list of alert instances', () => { const alert = mockAlert(); + const alertType = mockAlertType(); const alertInstanceSummary = mockAlertInstanceSummary({ instances: { first_instance: { status: 'OK', muted: false, + actionGroupId: 'default', }, second_instance: { status: 'Active', muted: false, + actionGroupId: 'action group id unknown', }, }, }); @@ -51,14 +54,14 @@ describe('alert_instances', () => { // active first alertInstanceToListItem( fakeNow.getTime(), - alert, + alertType, 'second_instance', alertInstanceSummary.instances.second_instance ), // ok second alertInstanceToListItem( fakeNow.getTime(), - alert, + alertType, 'first_instance', alertInstanceSummary.instances.first_instance ), @@ -69,6 +72,7 @@ describe('alert_instances', () => { @@ -80,6 +84,7 @@ describe('alert_instances', () => { it('render a hidden field with duration epoch', () => { const alert = mockAlert(); + const alertType = mockAlertType(); const alertInstanceSummary = mockAlertInstanceSummary(); expect( @@ -88,6 +93,7 @@ describe('alert_instances', () => { durationEpoch={fake2MinutesAgo.getTime()} {...mockAPIs} alert={alert} + alertType={alertType} readOnly={false} alertInstanceSummary={alertInstanceSummary} /> @@ -99,6 +105,7 @@ describe('alert_instances', () => { it('render all active alert instances', () => { const alert = mockAlert(); + const alertType = mockAlertType(); const instances: Record = { ['us-central']: { status: 'OK', @@ -114,6 +121,7 @@ describe('alert_instances', () => { { .find(EuiBasicTable) .prop('items') ).toEqual([ - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-central', instances['us-central']), - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-east', instances['us-east']), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-central', instances['us-central']), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-east', instances['us-east']), ]); }); @@ -132,6 +140,7 @@ describe('alert_instances', () => { const alert = mockAlert({ mutedInstanceIds: ['us-west', 'us-east'], }); + const alertType = mockAlertType(); const instanceUsWest: AlertInstanceStatus = { status: 'OK', muted: false }; const instanceUsEast: AlertInstanceStatus = { status: 'OK', muted: false }; @@ -140,6 +149,7 @@ describe('alert_instances', () => { { .find(EuiBasicTable) .prop('items') ).toEqual([ - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-west', instanceUsWest), - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-east', instanceUsEast), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-west', instanceUsWest), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-east', instanceUsEast), ]); }); }); describe('alertInstanceToListItem', () => { it('handles active instances', () => { - const alert = mockAlert(); + const alertType = mockAlertType({ + actionGroups: [ + { id: 'default', name: 'Default Action Group' }, + { id: 'testing', name: 'Test Action Group' }, + ], + }); const start = fake2MinutesAgo; const instance: AlertInstanceStatus = { status: 'Active', muted: false, activeStartDate: fake2MinutesAgo.toISOString(), + actionGroupId: 'testing', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', - status: { label: 'Active', healthColor: 'primary' }, + status: { label: 'Active', actionGroup: 'Test Action Group', healthColor: 'primary' }, start, sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), @@ -184,20 +200,38 @@ describe('alertInstanceToListItem', () => { }); }); - it('handles active muted instances', () => { - const alert = mockAlert({ - mutedInstanceIds: ['id'], + it('handles active instances with no action group id', () => { + const alertType = mockAlertType(); + const start = fake2MinutesAgo; + const instance: AlertInstanceStatus = { + status: 'Active', + muted: false, + activeStartDate: fake2MinutesAgo.toISOString(), + }; + + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ + instance: 'id', + status: { label: 'Active', actionGroup: 'Default Action Group', healthColor: 'primary' }, + start, + sortPriority: 0, + duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), + isMuted: false, }); + }); + + it('handles active muted instances', () => { + const alertType = mockAlertType(); const start = fake2MinutesAgo; const instance: AlertInstanceStatus = { status: 'Active', muted: true, activeStartDate: fake2MinutesAgo.toISOString(), + actionGroupId: 'default', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', - status: { label: 'Active', healthColor: 'primary' }, + status: { label: 'Active', actionGroup: 'Default Action Group', healthColor: 'primary' }, start, sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), @@ -206,15 +240,16 @@ describe('alertInstanceToListItem', () => { }); it('handles active instances with start date', () => { - const alert = mockAlert(); + const alertType = mockAlertType(); const instance: AlertInstanceStatus = { status: 'Active', muted: false, + actionGroupId: 'default', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', - status: { label: 'Active', healthColor: 'primary' }, + status: { label: 'Active', actionGroup: 'Default Action Group', healthColor: 'primary' }, start: undefined, duration: 0, sortPriority: 0, @@ -223,14 +258,13 @@ describe('alertInstanceToListItem', () => { }); it('handles muted inactive instances', () => { - const alert = mockAlert({ - mutedInstanceIds: ['id'], - }); + const alertType = mockAlertType(); const instance: AlertInstanceStatus = { status: 'OK', muted: true, + actionGroupId: 'default', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', status: { label: 'OK', healthColor: 'subdued' }, start: undefined, @@ -268,6 +302,23 @@ function mockAlert(overloads: Partial = {}): Alert { }; } +function mockAlertType(overloads: Partial = {}): AlertType { + return { + id: 'test.testAlertType', + name: 'My Test Alert Type', + actionGroups: [{ id: 'default', name: 'Default Action Group' }], + actionVariables: { + context: [], + state: [], + params: [], + }, + defaultActionGroupId: 'default', + authorizedConsumers: {}, + producer: 'alerts', + ...overloads, + }; +} + function mockAlertInstanceSummary( overloads: Partial = {} ): AlertInstanceSummary { @@ -288,6 +339,7 @@ function mockAlertInstanceSummary( foo: { status: 'OK', muted: false, + actionGroupId: 'testActionGroup', }, }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx index 0648f34927db32..ed05d81646c4af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx @@ -11,8 +11,14 @@ import { EuiBasicTable, EuiHealth, EuiSpacer, EuiSwitch } from '@elastic/eui'; // @ts-ignore import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '@elastic/eui/lib/services'; import { padStart, chunk } from 'lodash'; -import { AlertInstanceStatusValues } from '../../../../../../alerts/common'; -import { Alert, AlertInstanceSummary, AlertInstanceStatus, Pagination } from '../../../../types'; +import { ActionGroup, AlertInstanceStatusValues } from '../../../../../../alerts/common'; +import { + Alert, + AlertInstanceSummary, + AlertInstanceStatus, + AlertType, + Pagination, +} from '../../../../types'; import { ComponentOpts as AlertApis, withBulkAlertOperations, @@ -21,6 +27,7 @@ import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; type AlertInstancesProps = { alert: Alert; + alertType: AlertType; readOnly: boolean; alertInstanceSummary: AlertInstanceSummary; requestRefresh: () => Promise; @@ -48,7 +55,12 @@ export const alertInstancesTableColumns = ( { defaultMessage: 'Status' } ), render: (value: AlertInstanceListItemStatus, instance: AlertInstanceListItem) => { - return {value.label}; + return ( + + {value.label} + {value.actionGroup ? ` (${value.actionGroup})` : ``} + + ); }, sortable: false, 'data-test-subj': 'alertInstancesTableCell-status', @@ -113,6 +125,7 @@ function durationAsString(duration: Duration): string { export function AlertInstances({ alert, + alertType, readOnly, alertInstanceSummary, muteAlertInstance, @@ -127,7 +140,7 @@ export function AlertInstances({ const alertInstances = Object.entries(alertInstanceSummary.instances) .map(([instanceId, instance]) => - alertInstanceToListItem(durationEpoch, alert, instanceId, instance) + alertInstanceToListItem(durationEpoch, alertType, instanceId, instance) ) .sort((leftInstance, rightInstance) => leftInstance.sortPriority - rightInstance.sortPriority); @@ -180,6 +193,7 @@ function getPage(items: any[], pagination: Pagination) { interface AlertInstanceListItemStatus { label: string; healthColor: string; + actionGroup?: string; } export interface AlertInstanceListItem { instance: string; @@ -200,16 +214,28 @@ const INACTIVE_LABEL = i18n.translate( { defaultMessage: 'OK' } ); +function getActionGroupName(alertType: AlertType, actionGroupId?: string): string | undefined { + actionGroupId = actionGroupId || alertType.defaultActionGroupId; + const actionGroup = alertType?.actionGroups?.find( + (group: ActionGroup) => group.id === actionGroupId + ); + return actionGroup?.name; +} + export function alertInstanceToListItem( durationEpoch: number, - alert: Alert, + alertType: AlertType, instanceId: string, instance: AlertInstanceStatus ): AlertInstanceListItem { const isMuted = !!instance?.muted; const status = instance?.status === 'Active' - ? { label: ACTIVE_LABEL, healthColor: 'primary' } + ? { + label: ACTIVE_LABEL, + actionGroup: getActionGroupName(alertType, instance?.actionGroupId), + healthColor: 'primary', + } : { label: INACTIVE_LABEL, healthColor: 'subdued' }; const start = instance?.activeStartDate ? new Date(instance.activeStartDate) : undefined; const duration = start ? durationEpoch - start.valueOf() : 0; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index 603f06d0bbae44..3a171d469d4ad6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -8,7 +8,7 @@ import uuid from 'uuid'; import { shallow } from 'enzyme'; import { ToastsApi } from 'kibana/public'; import { AlertInstancesRoute, getAlertInstanceSummary } from './alert_instances_route'; -import { Alert, AlertInstanceSummary } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; import { EuiLoadingSpinner } from '@elastic/eui'; const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -23,10 +23,11 @@ jest.mock('../../../app_context', () => { describe('alert_instance_summary_route', () => { it('render a loader while fetching data', () => { const alert = mockAlert(); + const alertType = mockAlertType(); expect( shallow( - + ).containsMatchingElement() ).toBeTruthy(); }); @@ -140,6 +141,23 @@ function mockAlert(overloads: Partial = {}): Alert { }; } +function mockAlertType(overloads: Partial = {}): AlertType { + return { + id: 'test.testAlertType', + name: 'My Test Alert Type', + actionGroups: [{ id: 'default', name: 'Default Action Group' }], + actionVariables: { + context: [], + state: [], + params: [], + }, + defaultActionGroupId: 'default', + authorizedConsumers: {}, + producer: 'alerts', + ...overloads, + }; +} + function mockAlertInstanceSummary(overloads: Partial = {}): any { const summary: AlertInstanceSummary = { id: 'alert-id', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx index 9137a26a32dd45..83a09e9eafcc15 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { ToastsApi } from 'kibana/public'; import React, { useState, useEffect } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { Alert, AlertInstanceSummary } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; import { useAppDependencies } from '../../../app_context'; import { ComponentOpts as AlertApis, @@ -18,12 +18,14 @@ import { AlertInstancesWithApi as AlertInstances } from './alert_instances'; type WithAlertInstanceSummaryProps = { alert: Alert; + alertType: AlertType; readOnly: boolean; requestRefresh: () => Promise; } & Pick; export const AlertInstancesRoute: React.FunctionComponent = ({ alert, + alertType, readOnly, requestRefresh, loadAlertInstanceSummary: loadAlertInstanceSummary, @@ -48,6 +50,7 @@ export const AlertInstancesRoute: React.FunctionComponent diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index a5dff437283aec..dbf8eb162fca71 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -134,7 +134,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { validateInstanceEvent(event, `resolved instance: 'instance'`); break; case 'active-instance': - validateInstanceEvent(event, `active instance: 'instance'`); + validateInstanceEvent(event, `active instance: 'instance' in actionGroup: 'default'`); break; // this will get triggered as we add new event actions default: diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts index 563127e028a628..22034328e5275a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts @@ -226,6 +226,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr instanceA: { status: 'Active', muted: false, + actionGroupId: 'default', activeStartDate: actualInstances.instanceA.activeStartDate, }, instanceB: { @@ -235,6 +236,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr instanceC: { status: 'Active', muted: true, + actionGroupId: 'default', activeStartDate: actualInstances.instanceC.activeStartDate, }, instanceD: { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 4c97c8556d7dff..9e4006681dc8dd 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -392,21 +392,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(instancesList.map((instance) => omit(instance, 'duration'))).to.eql([ { instance: 'us-central', - status: 'Active', + status: 'Active (Default)', start: moment(dateOnAllInstancesFromApiResponse['us-central']) .utc() .format('D MMM YYYY @ HH:mm:ss'), }, { instance: 'us-east', - status: 'Active', + status: 'Active (Default)', start: moment(dateOnAllInstancesFromApiResponse['us-east']) .utc() .format('D MMM YYYY @ HH:mm:ss'), }, { instance: 'us-west', - status: 'Active', + status: 'Active (Default)', start: moment(dateOnAllInstancesFromApiResponse['us-west']) .utc() .format('D MMM YYYY @ HH:mm:ss'), From 94d0e6070631b4bd1930666159edba86dce81c16 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 6 Nov 2020 13:47:55 +0100 Subject: [PATCH 25/81] [Lens] Implement time scaling function (#82104) --- src/plugins/data/common/search/aggs/types.ts | 2 +- .../aggs/utils/time_column_meta.test.ts | 41 +- .../search/aggs/utils/time_column_meta.ts | 10 +- .../data/public/search/expressions/esaggs.ts | 7 +- .../public/indexpattern_datasource/index.ts | 22 +- .../indexpattern_datasource/indexpattern.tsx | 1 + .../time_scale.test.ts | 368 ++++++++++++++++++ .../indexpattern_datasource/time_scale.ts | 167 ++++++++ 8 files changed, 601 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index 09a13762d4d704..897b60e91b100b 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -94,7 +94,7 @@ export interface AggsCommonStart { */ getDateMetaByDatatableColumn: ( column: DatatableColumn - ) => Promise; + ) => Promise; createAggConfigs: ( indexPattern: IndexPattern, configStates?: CreateAggConfigParams[], diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts index e56d6227345547..8eb076f5b79065 100644 --- a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts +++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts @@ -91,6 +91,43 @@ describe('getDateMetaByDatatableColumn', () => { }); }); + it('throws if unable to resolve interval', async () => { + await expect( + getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + time_zone: 'UTC', + interval: 'auto', + }, + }, + }, + }) + ).rejects.toBeDefined(); + + await expect( + getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + time_zone: 'UTC', + }, + }, + }, + }) + ).rejects.toBeDefined(); + }); + it('returns resolved auto interval', async () => { expect( await getDateMetaByDatatableColumn(params)({ @@ -106,8 +143,8 @@ describe('getDateMetaByDatatableColumn', () => { interval: 'auto', }, appliedTimeRange: { - from: 'now-5d', - to: 'now', + from: '2020-10-05T00:00:00.000Z', + to: '2020-10-10T00:00:00.000Z', }, }, }, diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts index 1bea716c6a0490..7ed8cb79f63f47 100644 --- a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts +++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts @@ -38,11 +38,11 @@ export const getDateMetaByDatatableColumn = ({ getConfig, }: DateMetaByColumnDeps) => async ( column: DatatableColumn -): Promise => { +): Promise => { if (column.meta.source !== 'esaggs') return; if (column.meta.sourceParams?.type !== BUCKET_TYPES.DATE_HISTOGRAM) return; const params = column.meta.sourceParams.params as AggParamsDateHistogram; - const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange; + const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange | undefined; const tz = inferTimeZone( params, @@ -52,9 +52,11 @@ export const getDateMetaByDatatableColumn = ({ ); const interval = - params.interval === 'auto' ? calculateAutoTimeExpression(appliedTimeRange) : params.interval; + params.interval === 'auto' && appliedTimeRange + ? calculateAutoTimeExpression(appliedTimeRange) + : params.interval; - if (!interval) { + if (!interval || interval === 'auto') { throw new Error('time interval could not be determined'); } diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index dba77d398c8b67..3932484801fa81 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -267,6 +267,8 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ searchSource.setField('index', indexPattern); searchSource.setField('size', 0); + const resolvedTimeRange = input?.timeRange && calculateBounds(input.timeRange); + const response = await handleCourierRequest({ searchSource, aggs, @@ -303,7 +305,10 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ input?.timeRange && args.timeFields && args.timeFields.includes(column.aggConfig.params.field?.name) - ? { from: input.timeRange.from, to: input.timeRange.to } + ? { + from: resolvedTimeRange?.min?.toISOString(), + to: resolvedTimeRange?.max?.toISOString(), + } : undefined, ...column.aggConfig.serialize(), }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 35987656f66703..92280b0fb6ce6f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -33,19 +33,23 @@ export class IndexPatternDatasource { { expressions, editorFrame, charts }: IndexPatternDatasourceSetupPlugins ) { editorFrame.registerDatasource(async () => { - const { getIndexPatternDatasource, renameColumns, formatColumn } = await import( - '../async_services' - ); - expressions.registerFunction(renameColumns); - expressions.registerFunction(formatColumn); - return core.getStartServices().then(([coreStart, { data }]) => - getIndexPatternDatasource({ + const { + getIndexPatternDatasource, + renameColumns, + formatColumn, + getTimeScaleFunction, + } = await import('../async_services'); + return core.getStartServices().then(([coreStart, { data }]) => { + expressions.registerFunction(getTimeScaleFunction(data)); + expressions.registerFunction(renameColumns); + expressions.registerFunction(formatColumn); + return getIndexPatternDatasource({ core: coreStart, storage: new Storage(localStorage), data, charts, - }) - ) as Promise; + }); + }) as Promise; }); } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 0d822927808084..ecca1b878e9a7d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -76,6 +76,7 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri export * from './rename_columns'; export * from './format_column'; +export * from './time_scale'; export function getIndexPatternDatasource({ core, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts new file mode 100644 index 00000000000000..c29e2cd9567dc8 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts @@ -0,0 +1,368 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 moment from 'moment'; +import { Datatable } from 'src/plugins/expressions/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; +import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; +import { getTimeScaleFunction, TimeScaleArgs } from './time_scale'; + +describe('time_scale', () => { + let timeScale: (input: Datatable, args: TimeScaleArgs) => Promise; + let dataMock: jest.Mocked; + + const emptyTable: Datatable = { + type: 'datatable', + columns: [ + { + id: 'date', + name: 'date', + meta: { + type: 'date', + }, + }, + { + id: 'metric', + name: 'metric', + meta: { + type: 'number', + }, + }, + ], + rows: [], + }; + + const defaultArgs: TimeScaleArgs = { + dateColumnId: 'date', + inputColumnId: 'metric', + outputColumnId: 'scaledMetric', + targetUnit: 'h', + }; + + beforeEach(() => { + dataMock = dataPluginMock.createStartContract(); + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T00:00:00.000Z', + to: '2020-10-10T00:00:00.000Z', + }, + interval: '1d', + }); + (dataMock.query.timefilter.timefilter.calculateBounds as jest.Mock).mockImplementation( + ({ from, to }) => ({ + min: moment(from), + max: moment(to), + }) + ); + timeScale = functionWrapper(getTimeScaleFunction(dataMock)); + }); + + it('should apply time scale factor to each row', async () => { + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-06T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-07T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-08T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-09T00:00:00.000Z').valueOf(), + metric: 24, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]); + }); + + it('should skip gaps in the data', async () => { + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-06T00:00:00.000Z').valueOf(), + }, + { + date: moment('2020-10-07T00:00:00.000Z').valueOf(), + }, + { + date: moment('2020-10-08T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-09T00:00:00.000Z').valueOf(), + metric: 24, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([ + 1, + undefined, + undefined, + 1, + 1, + ]); + }); + + it('should return input unchanged if input column does not exist', async () => { + const mismatchedTable = { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 24, + }, + ], + }; + const result = await timeScale(mismatchedTable, { + ...defaultArgs, + inputColumnId: 'nonexistent', + }); + + expect(result).toBe(mismatchedTable); + }); + + it('should be able to scale up as well', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T12:00:00.000Z', + to: '2020-10-05T16:00:00.000Z', + }, + interval: '1h', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T12:00:00.000Z').valueOf(), + metric: 1, + }, + { + date: moment('2020-10-05T13:00:00.000Z').valueOf(), + metric: 1, + }, + { + date: moment('2020-10-05T14:00:00.000Z').valueOf(), + metric: 1, + }, + { + date: moment('2020-10-05T15:00:00.000Z').valueOf(), + metric: 1, + }, + ], + }, + { + ...defaultArgs, + targetUnit: 'd', + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([24, 24, 24, 24]); + }); + + it('can scale starting from unit multiple target intervals', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T13:00:00.000Z', + to: '2020-10-05T23:00:00.000Z', + }, + interval: '3h', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + // bucket is cut off by one hour because of the time range + date: moment('2020-10-05T12:00:00.000Z').valueOf(), + metric: 2, + }, + { + date: moment('2020-10-05T15:00:00.000Z').valueOf(), + metric: 3, + }, + { + date: moment('2020-10-05T18:00:00.000Z').valueOf(), + metric: 3, + }, + { + // bucket is cut off by one hour because of the time range + date: moment('2020-10-05T21:00:00.000Z').valueOf(), + metric: 2, + }, + ], + }, + { + ...defaultArgs, + targetUnit: 'h', + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1]); + }); + + it('take start and end of timerange into account', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T12:00:00.000Z', + to: '2020-10-09T12:00:00.000Z', + }, + interval: '1d', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + // this is a partial bucket because it starts before the start of the time range + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 12, + }, + { + date: moment('2020-10-06T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-07T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-08T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + // this is a partial bucket because it ends earlier than the regular interval of 1d + date: moment('2020-10-09T00:00:00.000Z').valueOf(), + metric: 12, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]); + }); + + it('should respect DST switches', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'Europe/Berlin', + timeRange: { + from: '2020-10-23T00:00:00.000+02:00', + to: '2020-10-27T00:00:00.000+01:00', + }, + interval: '1d', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-23T00:00:00.000+02:00').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-24T00:00:00.000+02:00').valueOf(), + metric: 24, + }, + { + // this day has one hour more in Europe/Berlin due to DST switch + date: moment('2020-10-25T00:00:00.000+02:00').valueOf(), + metric: 25, + }, + { + date: moment('2020-10-26T00:00:00.000+01:00').valueOf(), + metric: 24, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1]); + }); + + it('take leap years into account', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2010-01-01T00:00:00.000Z', + to: '2015-01-01T00:00:00.000Z', + }, + interval: '1y', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2010-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + { + date: moment('2011-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + { + // 2012 is a leap year and has an additional day + date: moment('2012-01-01T00:00:00.000Z').valueOf(), + metric: 366, + }, + { + date: moment('2013-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + { + date: moment('2014-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + ], + }, + { + ...defaultArgs, + targetUnit: 'd', + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts new file mode 100644 index 00000000000000..0937f40eeb6d3d --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 moment from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { search } from '../../../../../src/plugins/data/public'; + +type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; + +export interface TimeScaleArgs { + dateColumnId: string; + inputColumnId: string; + outputColumnId: string; + targetUnit: TimeScaleUnit; + outputColumnName?: string; +} + +const unitInMs: Record = { + s: 1000, + m: 1000 * 60, + h: 1000 * 60 * 60, + d: 1000 * 60 * 60 * 24, +}; + +export function getTimeScaleFunction(data: DataPublicPluginStart) { + const timeScale: ExpressionFunctionDefinition< + 'lens_time_scale', + Datatable, + TimeScaleArgs, + Promise + > = { + name: 'lens_time_scale', + type: 'datatable', + help: '', + args: { + dateColumnId: { + types: ['string'], + help: '', + required: true, + }, + inputColumnId: { + types: ['string'], + help: '', + required: true, + }, + outputColumnId: { + types: ['string'], + help: '', + required: true, + }, + outputColumnName: { + types: ['string'], + help: '', + }, + targetUnit: { + types: ['string'], + options: ['s', 'm', 'h', 'd'], + help: '', + required: true, + }, + }, + inputTypes: ['datatable'], + async fn( + input, + { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs + ) { + if (input.columns.some((column) => column.id === outputColumnId)) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.columnConflictMessage', { + defaultMessage: 'Specified outputColumnId {columnId} already exists.', + values: { + columnId: outputColumnId, + }, + }) + ); + } + + const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); + + if (!dateColumnDefinition) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { + defaultMessage: 'Specified dateColumnId {columnId} does not exist.', + values: { + columnId: dateColumnId, + }, + }) + ); + } + + const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + + if (!inputColumnDefinition) { + return input; + } + + const outputColumnDefinition = { + ...inputColumnDefinition, + id: outputColumnId, + name: outputColumnName || outputColumnId, + }; + + const resultColumns = [...input.columns]; + // add output column after input column in the table + resultColumns.splice( + resultColumns.indexOf(inputColumnDefinition) + 1, + 0, + outputColumnDefinition + ); + + const targetUnitInMs = unitInMs[targetUnit]; + const timeInfo = await data.search.aggs.getDateMetaByDatatableColumn(dateColumnDefinition); + const intervalDuration = timeInfo && search.aggs.parseInterval(timeInfo.interval); + + if (!timeInfo || !intervalDuration) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { + defaultMessage: 'Could not fetch date histogram information', + }) + ); + } + // the datemath plugin always parses dates by using the current default moment time zone. + // to use the configured time zone, we are switching just for the bounds calculation. + const defaultTimezone = moment().zoneName(); + moment.tz.setDefault(timeInfo.timeZone); + + const timeBounds = + timeInfo.timeRange && data.query.timefilter.timefilter.calculateBounds(timeInfo.timeRange); + + const result = { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + let startOfBucket = moment(row[dateColumnId]); + let endOfBucket = startOfBucket.clone().add(intervalDuration); + if (timeBounds && timeBounds.min) { + startOfBucket = moment.max(startOfBucket, timeBounds.min); + } + if (timeBounds && timeBounds.max) { + endOfBucket = moment.min(endOfBucket, timeBounds.max); + } + const bucketSize = endOfBucket.diff(startOfBucket); + const factor = bucketSize / targetUnitInMs; + + const currentValue = newRow[inputColumnId]; + if (currentValue != null) { + newRow[outputColumnId] = Number(currentValue) / factor; + } + + return newRow; + }), + }; + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); + + return result; + }, + }; + return timeScale; +} From 71ec5bd36b17bbd5702864fd911fec832cc61f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 6 Nov 2020 15:42:51 +0100 Subject: [PATCH 26/81] Add ILM url generator and use it in Index Management (#82165) * Add ILM url generator and use in IM for cross linking to policy edit page * Fix policy name in the link * Add review suggestions * Fix import * Fix eslint error Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../index_lifecycle_management/kibana.json | 3 +- .../public/application/app.tsx | 7 +- .../policy_table/components/table_content.tsx | 4 +- .../sections/policy_table/policy_table.tsx | 3 +- .../public/application/services/navigation.ts | 16 ++- .../components/index_lifecycle_summary.tsx | 4 +- .../public/index.ts | 2 + .../public/plugin.tsx | 9 +- .../public/types.ts | 4 +- .../public/url_generator.ts | 60 ++++++++++++ .../helpers/test_subjects.ts | 2 +- .../home/data_streams_tab.helpers.ts | 28 ++++-- .../home/data_streams_tab.test.ts | 97 +++++++++++++++++-- .../home/indices_tab.helpers.ts | 8 +- .../home/indices_tab.test.ts | 5 +- x-pack/plugins/index_management/kibana.json | 3 +- .../public/application/app_context.tsx | 2 + .../constants/ilm_url_generator.ts | 8 ++ .../public/application/constants/index.ts | 2 + .../application/mount_management_section.ts | 8 +- .../data_stream_detail_panel.tsx | 50 ++++++++-- .../data_stream_list/data_stream_list.tsx | 5 - .../template_details/tabs/tab_summary.tsx | 26 +++-- .../application/services/use_url_generator.ts | 39 ++++++++ .../plugins/index_management/public/index.ts | 4 +- .../plugins/index_management/public/plugin.ts | 27 +++--- .../plugins/index_management/public/types.ts | 20 ++++ 27 files changed, 362 insertions(+), 84 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/url_generator.ts create mode 100644 x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts create mode 100644 x-pack/plugins/index_management/public/application/services/use_url_generator.ts diff --git a/x-pack/plugins/index_lifecycle_management/kibana.json b/x-pack/plugins/index_lifecycle_management/kibana.json index 1b0a73c6a01333..21e7e7888acb92 100644 --- a/x-pack/plugins/index_lifecycle_management/kibana.json +++ b/x-pack/plugins/index_lifecycle_management/kibana.json @@ -6,7 +6,8 @@ "requiredPlugins": [ "licensing", "management", - "features" + "features", + "share" ], "optionalPlugins": [ "cloud", diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index f7f8b30324bcad..856981fe5c4f93 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -13,6 +13,7 @@ import { UIM_APP_LOAD } from './constants/ui_metric'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; +import { ROUTES } from './services/navigation'; export const App = ({ history, @@ -28,14 +29,14 @@ export const App = ({ return ( - + } /> } /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx index 7b6521fd8a9ef8..09c81efe163b58 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx @@ -35,7 +35,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { getIndexListUri } from '../../../../../../index_management/public'; import { PolicyFromES } from '../../../../../common/types'; -import { getPolicyPath } from '../../../services/navigation'; +import { getPolicyEditPath } from '../../../services/navigation'; import { sortTable } from '../../../services'; import { trackUiMetric } from '../../../services/ui_metric'; @@ -229,7 +229,7 @@ export const TableContent: React.FunctionComponent = ({ return ( + {...reactRouterNavigate(history, getPolicyEditPath(value as string), () => trackUiMetric(METRIC_TYPE.CLICK, UIM_EDIT_CLICK) )} > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx index 0c396dae757831..55987bd9a62a99 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx @@ -26,6 +26,7 @@ import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_rea import { PolicyFromES } from '../../../../common/types'; import { filterItems } from '../../services'; import { TableContent } from './components/table_content'; +import { getPolicyCreatePath } from '../../services/navigation'; interface Props { policies: PolicyFromES[]; @@ -45,7 +46,7 @@ export const PolicyTable: React.FunctionComponent = ({ const createPolicyButton = ( { +export const ROUTES = { + list: '/policies', + edit: '/policies/edit/:policyName?', + create: '/policies/edit', +}; + +export const getPolicyEditPath = (policyName: string): string => { return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; + +export const getPolicyCreatePath = () => { + return ROUTES.create; +}; + +export const getPoliciesListPath = () => { + return ROUTES.list; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx index afdf726ea02f99..80c8e1414e1f8a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx @@ -25,7 +25,7 @@ import { } from '@elastic/eui'; import { ApplicationStart } from 'kibana/public'; -import { getPolicyPath } from '../../application/services/navigation'; +import { getPolicyEditPath } from '../../application/services/navigation'; import { Index, IndexLifecyclePolicy } from '../../../common/types'; const getHeaders = (): Array<[keyof IndexLifecyclePolicy, string]> => { @@ -192,7 +192,7 @@ export class IndexLifecycleSummary extends Component { content = ( {value} diff --git a/x-pack/plugins/index_lifecycle_management/public/index.ts b/x-pack/plugins/index_lifecycle_management/public/index.ts index 586763188a54b1..2aee76cd8b1367 100644 --- a/x-pack/plugins/index_lifecycle_management/public/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/index.ts @@ -12,3 +12,5 @@ import { IndexLifecycleManagementPlugin } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => { return new IndexLifecycleManagementPlugin(initializerContext); }; + +export { ILM_URL_GENERATOR_ID, IlmUrlGeneratorState } from './url_generator'; diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 6300c6bfc7eb1d..deef5cfe6ef2c9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -14,14 +14,15 @@ import { init as initUiMetric } from './application/services/ui_metric'; import { init as initNotification } from './application/services/notification'; import { BreadcrumbService } from './application/services/breadcrumbs'; import { addAllExtensions } from './extend_index_management'; -import { PluginsDependencies, ClientConfigType } from './types'; +import { ClientConfigType, SetupDependencies } from './types'; +import { registerUrlGenerator } from './url_generator'; export class IndexLifecycleManagementPlugin { constructor(private readonly initializerContext: PluginInitializerContext) {} private breadcrumbService = new BreadcrumbService(); - public setup(coreSetup: CoreSetup, plugins: PluginsDependencies) { + public setup(coreSetup: CoreSetup, plugins: SetupDependencies) { const { ui: { enabled: isIndexLifecycleManagementUiEnabled }, } = this.initializerContext.config.get(); @@ -34,7 +35,7 @@ export class IndexLifecycleManagementPlugin { getStartServices, } = coreSetup; - const { usageCollection, management, indexManagement, home, cloud } = plugins; + const { usageCollection, management, indexManagement, home, cloud, share } = plugins; // Initialize services even if the app isn't mounted, because they're used by index management extensions. initHttp(http); @@ -102,6 +103,8 @@ export class IndexLifecycleManagementPlugin { if (indexManagement) { addAllExtensions(indexManagement.extensionsService); } + + registerUrlGenerator(coreSetup, management, share); } } diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts index 6b11830b424af0..1ce43957b1444b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/types.ts @@ -9,15 +9,17 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { ManagementSetup } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { CloudSetup } from '../../cloud/public'; +import { SharePluginSetup } from '../../../../src/plugins/share/public'; import { BreadcrumbService } from './application/services/breadcrumbs'; -export interface PluginsDependencies { +export interface SetupDependencies { usageCollection?: UsageCollectionSetup; management: ManagementSetup; cloud?: CloudSetup; indexManagement?: IndexManagementPluginSetup; home?: HomePublicPluginSetup; + share: SharePluginSetup; } export interface ClientConfigType { diff --git a/x-pack/plugins/index_lifecycle_management/public/url_generator.ts b/x-pack/plugins/index_lifecycle_management/public/url_generator.ts new file mode 100644 index 00000000000000..a884c9a54a4b81 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/url_generator.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'kibana/public'; +import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public/'; +import { + getPoliciesListPath, + getPolicyCreatePath, + getPolicyEditPath, +} from './application/services/navigation'; +import { MANAGEMENT_APP_ID } from '../../../../src/plugins/management/public'; +import { SetupDependencies } from './types'; +import { PLUGIN } from '../common/constants'; + +export const ILM_URL_GENERATOR_ID = 'ILM_URL_GENERATOR_ID'; + +export interface IlmUrlGeneratorState { + page: 'policies_list' | 'policy_edit' | 'policy_create'; + policyName?: string; + absolute?: boolean; +} +export const createIlmUrlGenerator = ( + getAppBasePath: (absolute?: boolean) => Promise +): UrlGeneratorsDefinition => { + return { + id: ILM_URL_GENERATOR_ID, + createUrl: async (state: IlmUrlGeneratorState): Promise => { + switch (state.page) { + case 'policy_create': { + return `${await getAppBasePath(!!state.absolute)}${getPolicyCreatePath()}`; + } + case 'policy_edit': { + return `${await getAppBasePath(!!state.absolute)}${getPolicyEditPath(state.policyName!)}`; + } + case 'policies_list': { + return `${await getAppBasePath(!!state.absolute)}${getPoliciesListPath()}`; + } + } + }, + }; +}; + +export const registerUrlGenerator = ( + coreSetup: CoreSetup, + management: SetupDependencies['management'], + share: SetupDependencies['share'] +) => { + const getAppBasePath = async (absolute = false) => { + const [coreStart] = await coreSetup.getStartServices(); + return coreStart.application.getUrlForApp(MANAGEMENT_APP_ID, { + path: management.sections.section.data.getApp(PLUGIN.ID)!.basePath, + absolute, + }); + }; + + share.urlGenerators.registerUrlGenerator(createIlmUrlGenerator(getAppBasePath)); +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 8a610a04f8bb17..b5386dec342050 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -13,13 +13,13 @@ export type TestSubjects = | 'createTemplateButton' | 'dataStreamsEmptyPromptTemplateLink' | 'dataStreamTable' - | 'dataStreamTable' | 'deleteSystemTemplateCallOut' | 'deleteTemplateButton' | 'deleteTemplatesConfirmation' | 'documentationLink' | 'emptyPrompt' | 'filterList.filterItem' + | 'ilmPolicyLink' | 'includeStatsSwitch' | 'indexTable' | 'indexTableIncludeHiddenIndicesToggle' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 82bd858240e1e1..6bf6c11a37bb41 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -7,6 +7,7 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; +import { EuiDescriptionListDescription } from '@elastic/eui'; import { registerTestBed, TestBed, @@ -26,15 +27,17 @@ export interface DataStreamsTabTestBed extends TestBed { clickReloadButton: () => void; clickNameAt: (index: number) => void; clickIndicesAt: (index: number) => void; - clickDeletActionAt: (index: number) => void; + clickDeleteActionAt: (index: number) => void; clickConfirmDelete: () => void; - clickDeletDataStreamButton: () => void; + clickDeleteDataStreamButton: () => void; }; findDeleteActionAt: (index: number) => ReactWrapper; findDeleteConfirmationModal: () => ReactWrapper; findDetailPanel: () => ReactWrapper; findDetailPanelTitle: () => string; findEmptyPromptIndexTemplateLink: () => ReactWrapper; + findDetailPanelIlmPolicyLink: () => ReactWrapper; + findDetailPanelIlmPolicyName: () => ReactWrapper; } export const setup = async (overridingDependencies: any = {}): Promise => { @@ -115,7 +118,7 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const clickDeleteActionAt = (index: number) => { findDeleteActionAt(index).simulate('click'); }; @@ -135,7 +138,7 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const clickDeleteDataStreamButton = () => { const { find } = testBed; find('deleteDataStreamButton').simulate('click'); }; @@ -150,6 +153,17 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find } = testBed; + return find('ilmPolicyLink'); + }; + + const findDetailPanelIlmPolicyName = () => { + const descriptionList = testBed.component.find(EuiDescriptionListDescription); + // ilm policy is the last in the details list + return descriptionList.last(); + }; + return { ...testBed, actions: { @@ -159,15 +173,17 @@ export const setup = async (overridingDependencies: any = {}): Promise { ]); setLoadDataStreamResponse(dataStreamForDetailPanel); - testBed = await setup(); + testBed = await setup({ history: createMemoryHistory() }); await act(async () => { testBed.actions.goToDataStreamsList(); }); @@ -176,19 +177,19 @@ describe('Data Streams tab', () => { describe('deleting a data stream', () => { test('shows a confirmation modal', async () => { const { - actions: { clickDeletActionAt }, + actions: { clickDeleteActionAt }, findDeleteConfirmationModal, } = testBed; - clickDeletActionAt(0); + clickDeleteActionAt(0); const confirmationModal = findDeleteConfirmationModal(); expect(confirmationModal).toBeDefined(); }); test('sends a request to the Delete API', async () => { const { - actions: { clickDeletActionAt, clickConfirmDelete }, + actions: { clickDeleteActionAt, clickConfirmDelete }, } = testBed; - clickDeletActionAt(0); + clickDeleteActionAt(0); httpRequestsMockHelpers.setDeleteDataStreamResponse({ results: { @@ -219,12 +220,12 @@ describe('Data Streams tab', () => { test('deletes the data stream when delete button is clicked', async () => { const { - actions: { clickNameAt, clickDeletDataStreamButton, clickConfirmDelete }, + actions: { clickNameAt, clickDeleteDataStreamButton, clickConfirmDelete }, } = testBed; await clickNameAt(0); - clickDeletDataStreamButton(); + clickDeleteDataStreamButton(); httpRequestsMockHelpers.setDeleteDataStreamResponse({ results: { @@ -263,7 +264,9 @@ describe('Data Streams tab', () => { setLoadDataStreamsResponse([dataStreamDollarSign]); setLoadDataStreamResponse(dataStreamDollarSign); - testBed = await setup(); + testBed = await setup({ + history: createMemoryHistory(), + }); await act(async () => { testBed.actions.goToDataStreamsList(); }); @@ -287,4 +290,82 @@ describe('Data Streams tab', () => { }); }); }); + + describe('url generators', () => { + const mockIlmUrlGenerator = { + getUrlGenerator: () => ({ + createUrl: ({ policyName }: { policyName: string }) => `/test/${policyName}`, + }), + }; + test('with an ILM url generator and an ILM policy', async () => { + const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; + + const dataStreamForDetailPanel = { + ...createDataStreamPayload('dataStream1'), + ilmPolicyName: 'my_ilm_policy', + }; + setLoadDataStreamsResponse([dataStreamForDetailPanel]); + setLoadDataStreamResponse(dataStreamForDetailPanel); + + testBed = await setup({ + history: createMemoryHistory(), + urlGenerators: mockIlmUrlGenerator, + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + + const { actions, findDetailPanelIlmPolicyLink } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelIlmPolicyLink().prop('href')).toBe('/test/my_ilm_policy'); + }); + + test('with an ILM url generator and no ILM policy', async () => { + const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; + + const dataStreamForDetailPanel = createDataStreamPayload('dataStream1'); + setLoadDataStreamsResponse([dataStreamForDetailPanel]); + setLoadDataStreamResponse(dataStreamForDetailPanel); + + testBed = await setup({ + history: createMemoryHistory(), + urlGenerators: mockIlmUrlGenerator, + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); + expect(findDetailPanelIlmPolicyName().contains('None')).toBeTruthy(); + }); + + test('without an ILM url generator and with an ILM policy', async () => { + const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; + + const dataStreamForDetailPanel = { + ...createDataStreamPayload('dataStream1'), + ilmPolicyName: 'my_ilm_policy', + }; + setLoadDataStreamsResponse([dataStreamForDetailPanel]); + setLoadDataStreamResponse(dataStreamForDetailPanel); + + testBed = await setup({ + history: createMemoryHistory(), + urlGenerators: { getUrlGenerator: () => {} }, + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); + expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy(); + }); + }); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index b660adb9eec08a..c7af3a8f451050 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -26,8 +26,6 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); - export interface IndicesTestBed extends TestBed { actions: { selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; @@ -39,7 +37,11 @@ export interface IndicesTestBed extends TestBed { findDataStreamDetailPanelTitle: () => string; } -export const setup = async (): Promise => { +export const setup = async (overridingDependencies: any = {}): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(IndexManagementHome, overridingDependencies), + testBedConfig + ); const testBed = await initTestBed(); /** diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index 3d6d94d1658559..adc47515acaee5 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -18,6 +18,7 @@ import { createDataStreamPayload } from './data_streams_tab.helpers'; at createWorker (//node_modules/brace/index.js:17992:5) */ import { stubWebWorker } from '../../../../../test_utils/stub_web_worker'; +import { createMemoryHistory } from 'history'; stubWebWorker(); describe('', () => { @@ -75,7 +76,9 @@ describe('', () => { httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1')); - testBed = await setup(); + testBed = await setup({ + history: createMemoryHistory(), + }); await act(async () => { const { component } = testBed; diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json index 28846414ca2e88..ff6a71d53894a3 100644 --- a/x-pack/plugins/index_management/kibana.json +++ b/x-pack/plugins/index_management/kibana.json @@ -7,7 +7,8 @@ "home", "licensing", "management", - "features" + "features", + "share" ], "optionalPlugins": [ "security", diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 22e6f09907d75d..c654288aaceb81 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -14,6 +14,7 @@ import { IngestManagerSetup } from '../../../ingest_manager/public'; import { IndexMgmtMetricsType } from '../types'; import { UiMetricService, NotificationService, HttpService } from './services'; import { ExtensionsService } from '../services'; +import { SharePluginStart } from '../../../../../src/plugins/share/public'; const AppContext = createContext(undefined); @@ -35,6 +36,7 @@ export interface AppDependencies { history: ScopedHistory; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; uiSettings: CoreSetup['uiSettings']; + urlGenerators: SharePluginStart['urlGenerators']; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts b/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts new file mode 100644 index 00000000000000..1b71e389ee3544 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 ILM_URL_GENERATOR_ID = 'ILM_URL_GENERATOR_ID'; +export const ILM_PAGES_POLICY_EDIT = 'policy_edit'; diff --git a/x-pack/plugins/index_management/public/application/constants/index.ts b/x-pack/plugins/index_management/public/application/constants/index.ts index d55d28dce70032..80c8779f2f020f 100644 --- a/x-pack/plugins/index_management/public/application/constants/index.ts +++ b/x-pack/plugins/index_management/public/application/constants/index.ts @@ -15,3 +15,5 @@ export { } from './detail_panel_tabs'; export const REACT_ROOT_ID = 'indexManagementReactRoot'; + +export * from './ilm_url_generator'; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index f7b728c8757627..52528d3c511454 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -12,7 +12,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { IngestManagerSetup } from '../../../ingest_manager/public'; import { PLUGIN } from '../../common/constants'; import { ExtensionsService } from '../services'; -import { IndexMgmtMetricsType } from '../types'; +import { IndexMgmtMetricsType, StartDependencies } from '../types'; import { AppDependencies } from './app_context'; import { breadcrumbService } from './services/breadcrumbs'; import { documentationService } from './services/documentation'; @@ -28,14 +28,14 @@ interface InternalServices { } export async function mountManagementSection( - coreSetup: CoreSetup, + coreSetup: CoreSetup, usageCollection: UsageCollectionSetup, services: InternalServices, params: ManagementAppMountParams, ingestManager?: IngestManagerSetup ) { const { element, setBreadcrumbs, history } = params; - const [core] = await coreSetup.getStartServices(); + const [core, startDependencies] = await coreSetup.getStartServices(); const { docLinks, fatalErrors, @@ -44,6 +44,7 @@ export async function mountManagementSection( uiSettings, } = core; + const { urlGenerators } = startDependencies.share; docTitle.change(PLUGIN.getI18nName(i18n)); breadcrumbService.setup(setBreadcrumbs); @@ -62,6 +63,7 @@ export async function mountManagementSection( history, setBreadcrumbs, uiSettings, + urlGenerators, }; const unmountAppCallback = renderApp(element, { core, dependencies: appDependencies }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index 0af22b497a4c0e..9ec6993717435a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -28,6 +28,10 @@ import { SectionLoading, SectionError, Error, DataHealth } from '../../../../com import { useLoadDataStream } from '../../../../services/api'; import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal'; import { humanizeTimeStamp } from '../humanize_time_stamp'; +import { useUrlGenerator } from '../../../../services/use_url_generator'; +import { ILM_PAGES_POLICY_EDIT, ILM_URL_GENERATOR_ID } from '../../../../constants'; +import { useAppContext } from '../../../../app_context'; +import { getIndexListUri } from '../../../../..'; interface DetailsListProps { details: Array<{ @@ -72,19 +76,26 @@ const DetailsList: React.FunctionComponent = ({ details }) => interface Props { dataStreamName: string; - backingIndicesLink: ReturnType; onClose: (shouldReload?: boolean) => void; } export const DataStreamDetailPanel: React.FunctionComponent = ({ dataStreamName, - backingIndicesLink, onClose, }) => { const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName); const [isDeleting, setIsDeleting] = useState(false); + const ilmPolicyLink = useUrlGenerator({ + urlGeneratorId: ILM_URL_GENERATOR_ID, + urlGeneratorState: { + page: ILM_PAGES_POLICY_EDIT, + policyName: dataStream?.ilmPolicyName, + }, + }); + const { history } = useAppContext(); + let content; if (isLoading) { @@ -159,7 +170,16 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', { defaultMessage: `The data stream's current backing indices`, }), - content: {indices.length}, + content: ( + + {indices.length} + + ), }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', { @@ -196,13 +216,23 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { defaultMessage: `The index lifecycle policy that manages the data stream's data`, }), - content: ilmPolicyName ?? ( - - {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { - defaultMessage: `None`, - })} - - ), + content: + ilmPolicyName && ilmPolicyLink ? ( + + {ilmPolicyName} + + ) : ( + ilmPolicyName || ( + + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { + defaultMessage: `None`, + })} + + ) + ), }, ]; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 19286523055f5c..ba79319b566bfa 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -28,7 +28,6 @@ import { import { useAppContext } from '../../../app_context'; import { SectionError, SectionLoading, Error } from '../../../components'; import { useLoadDataStreams } from '../../../services/api'; -import { getIndexListUri } from '../../../services/routing'; import { documentationService } from '../../../services/documentation'; import { Section } from '../home'; import { DataStreamTable } from './data_stream_table'; @@ -233,10 +232,6 @@ export const DataStreamList: React.FunctionComponent { history.push(`/${Section.DataStreams}`); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx index 0c403e69d2e765..961a171f9f05ad 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx @@ -19,9 +19,9 @@ import { EuiCodeBlock, EuiSpacer, } from '@elastic/eui'; -import { useAppContext } from '../../../../../app_context'; import { TemplateDeserialized } from '../../../../../../../common'; -import { getILMPolicyPath } from '../../../../../services/routing'; +import { ILM_PAGES_POLICY_EDIT, ILM_URL_GENERATOR_ID } from '../../../../../constants'; +import { useUrlGenerator } from '../../../../../services/use_url_generator'; interface Props { templateDetails: TemplateDeserialized; @@ -53,9 +53,13 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) const numIndexPatterns = indexPatterns.length; - const { - core: { getUrlForApp }, - } = useAppContext(); + const ilmPolicyLink = useUrlGenerator({ + urlGeneratorId: ILM_URL_GENERATOR_ID, + urlGeneratorState: { + page: ILM_PAGES_POLICY_EDIT, + policyName: ilmPolicy?.name, + }, + }); return ( <> @@ -159,16 +163,10 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) /> - {ilmPolicy && ilmPolicy.name ? ( - - {ilmPolicy.name} - + {ilmPolicy?.name && ilmPolicyLink ? ( + {ilmPolicy!.name} ) : ( - i18nTexts.none + ilmPolicy?.name || i18nTexts.none )} diff --git a/x-pack/plugins/index_management/public/application/services/use_url_generator.ts b/x-pack/plugins/index_management/public/application/services/use_url_generator.ts new file mode 100644 index 00000000000000..4e6bd05aa14b5c --- /dev/null +++ b/x-pack/plugins/index_management/public/application/services/use_url_generator.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 { useEffect, useState } from 'react'; +import { + UrlGeneratorContract, + UrlGeneratorId, + UrlGeneratorStateMapping, +} from '../../../../../../src/plugins/share/public'; +import { useAppContext } from '../app_context'; + +export const useUrlGenerator = ({ + urlGeneratorId, + urlGeneratorState, +}: { + urlGeneratorId: UrlGeneratorId; + urlGeneratorState: UrlGeneratorStateMapping[UrlGeneratorId]['State']; +}) => { + const { urlGenerators } = useAppContext(); + const [link, setLink] = useState(); + useEffect(() => { + const updateLink = async (): Promise => { + let urlGenerator: UrlGeneratorContract; + try { + urlGenerator = urlGenerators.getUrlGenerator(urlGeneratorId); + const url = await urlGenerator.createUrl(urlGeneratorState); + setLink(url); + } catch (e) { + // do nothing + } + }; + + updateLink(); + }, [urlGeneratorId, urlGeneratorState, urlGenerators]); + return link; +}; diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index 538dcaf25c47d1..da6d90f22a3840 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import './index.scss'; -import { IndexMgmtUIPlugin, IndexManagementPluginSetup } from './plugin'; +import { IndexMgmtUIPlugin } from './plugin'; /** @public */ export const plugin = () => { return new IndexMgmtUIPlugin(); }; -export { IndexManagementPluginSetup }; +export { IndexManagementPluginSetup } from './types'; export { getIndexListUri } from './application/services/routing'; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 6139ed5d2e6ad0..855486528b7974 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -6,10 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '../../../../src/core/public'; -import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; -import { IngestManagerSetup } from '../../ingest_manager/public'; import { UIM_APP_NAME, PLUGIN } from '../common/constants'; import { httpService } from './application/services/http'; @@ -19,18 +16,13 @@ import { UiMetricService } from './application/services/ui_metric'; import { setExtensionsService } from './application/store/selectors'; import { setUiMetricService } from './application/services/api'; -import { IndexMgmtMetricsType } from './types'; -import { ExtensionsService, ExtensionsSetup } from './services'; - -export interface IndexManagementPluginSetup { - extensionsService: ExtensionsSetup; -} - -interface PluginsDependencies { - ingestManager?: IngestManagerSetup; - usageCollection: UsageCollectionSetup; - management: ManagementSetup; -} +import { + IndexManagementPluginSetup, + IndexMgmtMetricsType, + SetupDependencies, + StartDependencies, +} from './types'; +import { ExtensionsService } from './services'; export class IndexMgmtUIPlugin { private uiMetricService = new UiMetricService(UIM_APP_NAME); @@ -43,7 +35,10 @@ export class IndexMgmtUIPlugin { setUiMetricService(this.uiMetricService); } - public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): IndexManagementPluginSetup { + public setup( + coreSetup: CoreSetup, + plugins: SetupDependencies + ): IndexManagementPluginSetup { const { http, notifications } = coreSetup; const { ingestManager, usageCollection, management } = plugins; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 08fbf634502187..f860b89b0ba0c7 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -4,4 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ExtensionsSetup } from './services'; +import { IngestManagerSetup } from '../../ingest_manager/public'; +import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { SharePluginStart } from '../../../../src/plugins/share/public'; + export type IndexMgmtMetricsType = 'loaded' | 'click' | 'count'; + +export interface IndexManagementPluginSetup { + extensionsService: ExtensionsSetup; +} + +export interface SetupDependencies { + ingestManager?: IngestManagerSetup; + usageCollection: UsageCollectionSetup; + management: ManagementSetup; +} + +export interface StartDependencies { + share: SharePluginStart; +} From b6d661f9c38f1c83a747e98b99594ecc8b567b76 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 6 Nov 2020 07:47:57 -0700 Subject: [PATCH 27/81] [Security Solutions][Detection Engine] Fixes critical clashing with source indexes that already contain a "signal" field (#82191) ## Summary Fixes: https://github.com/elastic/kibana/issues/82148 We have errors and do not generate a signal when a source index already has utilized and reserved the "signal" field for their own data purposes. This fix is a bit tricky and has one medium sized risk which is we also support "signals generated on top of existing signals". Therefore we have to be careful and do a small runtime detection of the "data shape" of the signal's data type. If it looks like the user is using the "signal" field within their mapping instead of us, we move the customer's signal into "original_signal" inside our "signal" structure we create when we copy their data set when creating a signal. To help mitigate the risks associated with this critical bug with regards to breaking signals on top of signals I have: * This adds unit tests * This adds end to end tests for testing generating signals including signals on signals to help mitigate risk The key test for this shape in the PR are in the file: ``` detection_engine/signals/build_event_type_signal.ts ``` like so: ```ts export const isEventTypeSignal = (doc: BaseSignalHit): boolean => { return doc._source.signal?.rule?.id != null && typeof doc._source.signal?.rule?.id === 'string'; }; ``` Example of what happens when it does a "move" of an existing numeric signal keyword type: ```ts # This causes a clash with us using the name signal as a numeric. PUT clashing-index/_doc/1 { "@timestamp": "2020-10-28T05:08:53.000Z", "signal": 1 } ``` Before, this was an error. With this PR it now will restructure this data like so when creating a signal along with additional signal ancestor information, meta data. I omitted some of the data from the output signal for this example. ```ts { ... Other data copied ... "signal": { "original_signal": 1 <--- We "move it" here now "parents": [ { "id": "BhbXBmkBR346wHgn4PeZ", "type": "event", "index": "your-index-name", "depth": 0 }, ], "ancestors": [ { "id": "BhbXBmkBR346wHgn4PeZ", "type": "event", "index": "your-index-name", "depth": 0 }, ], "status": "open", "depth": 1, "parent": { "id": "BhbXBmkBR346wHgn4PeZ", type: "event", "index": "your-index-name", "depth": 0 }, "original_time": "2019-02-19T17:40:03.790Z", "original_event": { "action": "socket_closed", "dataset": "socket", "kind": "event", "module": "system" }, } ``` ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../routes/index/signals_mapping.json | 5 + .../signals/build_bulk_body.test.ts | 202 ++++++- .../signals/build_bulk_body.ts | 2 +- .../signals/build_event_type_signal.test.ts | 56 +- .../signals/build_event_type_signal.ts | 13 + .../signals/build_signal.test.ts | 70 ++- .../detection_engine/signals/build_signal.ts | 31 +- .../signals/single_bulk_create.test.ts | 12 + .../signals/single_bulk_create.ts | 3 +- .../lib/detection_engine/signals/types.ts | 1 + .../tests/generating_signals.ts | 508 ++++++++++++++++++ .../security_and_spaces/tests/index.ts | 1 + .../detection_engine_api_integration/utils.ts | 29 + .../signals/numeric_name_clash/data.json | 12 + .../signals/numeric_name_clash/mappings.json | 20 + .../signals/object_clash/data.json | 12 + .../signals/object_clash/mappings.json | 20 + 17 files changed, 988 insertions(+), 9 deletions(-) create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts create mode 100644 x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json create mode 100644 x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json create mode 100644 x-pack/test/functional/es_archives/signals/object_clash/data.json create mode 100644 x-pack/test/functional/es_archives/signals/object_clash/mappings.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json index 7255325358baf6..4e9477f3f2f73b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json @@ -257,6 +257,11 @@ "original_time": { "type": "date" }, + "original_signal": { + "type": "object", + "dynamic": false, + "enabled": false + }, "original_event": { "properties": { "action": { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index 75a7de8cd2c443..ad060a9304e848 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -20,7 +20,7 @@ import { objectPairIntersection, objectArrayIntersection, } from './build_bulk_body'; -import { SignalHit } from './types'; +import { SignalHit, SignalSourceHit } from './types'; import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; describe('buildBulkBody', () => { @@ -441,6 +441,206 @@ describe('buildBulkBody', () => { }; expect(fakeSignalSourceHit).toEqual(expected); }); + + test('bulk body builds "original_signal" if it exists already as a numeric', () => { + const sampleParams = sampleRuleAlertParams(); + const sampleDoc = sampleDocNoSortId(); + delete sampleDoc._source.source; + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: 123, + }, + } as unknown) as SignalSourceHit; + const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + const expected: Omit & { someKey: string } = { + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_signal: 123, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + parents: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + ancestors: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + original_time: '2020-04-20T21:27:45+0000', + status: 'open', + rule: { + actions: [], + author: ['Elastic'], + building_block_type: 'default', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + risk_score_mapping: [], + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + license: 'Elastic License', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + severity_mapping: [], + tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], + type: 'query', + to: 'now', + note: '', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + version: 1, + updated_at: fakeSignalSourceHit.signal.rule?.updated_at, + created_at: fakeSignalSourceHit.signal.rule?.created_at, + throttle: 'no_actions', + exceptions_list: getListArrayMock(), + }, + depth: 1, + }, + }; + expect(fakeSignalSourceHit).toEqual(expected); + }); + + test('bulk body builds "original_signal" if it exists already as an object', () => { + const sampleParams = sampleRuleAlertParams(); + const sampleDoc = sampleDocNoSortId(); + delete sampleDoc._source.source; + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: { child_1: { child_2: 'nested data' } }, + }, + } as unknown) as SignalSourceHit; + const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + const expected: Omit & { someKey: string } = { + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_signal: { child_1: { child_2: 'nested data' } }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + parents: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + ancestors: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + original_time: '2020-04-20T21:27:45+0000', + status: 'open', + rule: { + actions: [], + author: ['Elastic'], + building_block_type: 'default', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + risk_score_mapping: [], + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + license: 'Elastic License', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + severity_mapping: [], + tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], + type: 'query', + to: 'now', + note: '', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + version: 1, + updated_at: fakeSignalSourceHit.signal.rule?.updated_at, + created_at: fakeSignalSourceHit.signal.rule?.created_at, + throttle: 'no_actions', + exceptions_list: getListArrayMock(), + }, + depth: 1, + }, + }; + expect(fakeSignalSourceHit).toEqual(expected); + }); }); describe('buildSignalFromSequence', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts index cc454ac1e94624..a704d076880bf3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts @@ -154,7 +154,7 @@ export const buildSignalFromEvent = ( const rule = applyOverrides ? buildRuleWithOverrides(ruleSO, event._source) : buildRuleWithoutOverrides(ruleSO); - const signal = { + const signal: Signal = { ...buildSignal([event], rule), ...additionalSignalFields(event), }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts index 106a049002e058..ada939ed0941a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts @@ -5,7 +5,8 @@ */ import { sampleDocNoSortId } from './__mocks__/es_results'; -import { buildEventTypeSignal } from './build_event_type_signal'; +import { buildEventTypeSignal, isEventTypeSignal } from './build_event_type_signal'; +import { BaseSignalHit } from './types'; describe('buildEventTypeSignal', () => { beforeEach(() => { @@ -44,4 +45,57 @@ describe('buildEventTypeSignal', () => { }; expect(eventType).toEqual(expected); }); + + test('It validates a sample doc with no signal type as "false"', () => { + const doc = sampleDocNoSortId(); + expect(isEventTypeSignal(doc)).toEqual(false); + }); + + test('It validates a sample doc with a signal type as "true"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: { + rule: { id: 'id-123' }, + }, + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(true); + }); + + test('It validates a numeric signal string as "false"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: 'something', + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(false); + }); + + test('It validates an empty object as "false"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: {}, + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(false); + }); + + test('It validates an empty rule object as "false"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: { + rule: {}, + }, + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(false); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts index 81c9d1dedcc56c..3d78cf5ce5e467 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts @@ -13,3 +13,16 @@ export const buildEventTypeSignal = (doc: BaseSignalHit): object => { return { kind: 'signal' }; } }; + +/** + * Given a document this will return true if that document is a signal + * document. We can't guarantee the code will call this function with a document + * before adding the _source.event.kind = "signal" from "buildEventTypeSignal" + * so we do basic testing to ensure that if the object has the fields of: + * "signal.rule.id" then it will be one of our signals rather than a customer + * overwritten signal. + * @param doc The document which might be a signal or it might be a regular log + */ +export const isEventTypeSignal = (doc: BaseSignalHit): boolean => { + return doc._source.signal?.rule?.id != null && typeof doc._source.signal?.rule?.id === 'string'; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts index d0c451bbdf2e2b..c5e6bc9f157e01 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts @@ -5,8 +5,14 @@ */ import { sampleDocNoSortId } from './__mocks__/es_results'; -import { buildSignal, buildParent, buildAncestors, additionalSignalFields } from './build_signal'; -import { Signal, Ancestor } from './types'; +import { + buildSignal, + buildParent, + buildAncestors, + additionalSignalFields, + removeClashes, +} from './build_signal'; +import { Signal, Ancestor, BaseSignalHit } from './types'; import { getRulesSchemaMock, ANCHOR_DATE, @@ -302,4 +308,64 @@ describe('buildSignal', () => { ]; expect(signal).toEqual(expected); }); + + describe('removeClashes', () => { + test('it will call renameClashes with a regular doc and not mutate it if it does not have a signal clash', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const output = removeClashes(doc); + expect(output).toBe(doc); // reference check + }); + + test('it will call renameClashes with a regular doc and not change anything', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const output = removeClashes(doc); + expect(output).toEqual(doc); // deep equal check + }); + + test('it will remove a "signal" numeric clash', () => { + const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: 127, + }, + } as unknown) as BaseSignalHit; + const output = removeClashes(doc); + expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')); + }); + + test('it will remove a "signal" object clash', () => { + const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: { child_1: { child_2: 'Test nesting' } }, + }, + } as unknown) as BaseSignalHit; + const output = removeClashes(doc); + expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')); + }); + + test('it will not remove a "signal" if that is signal is one of our signals', () => { + const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: { rule: { id: '123' } }, + }, + } as unknown) as BaseSignalHit; + const output = removeClashes(doc); + const expected = { + ...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'), + _source: { + ...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')._source, + signal: { rule: { id: '123' } }, + }, + }; + expect(output).toEqual(expected); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts index 947938de6caca6..b36a1cbb4a6b3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts @@ -5,6 +5,7 @@ */ import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; +import { isEventTypeSignal } from './build_event_type_signal'; import { Signal, Ancestor, BaseSignalHit } from './types'; /** @@ -48,15 +49,37 @@ export const buildAncestors = (doc: BaseSignalHit): Ancestor[] => { } }; +/** + * This removes any signal named clashes such as if a source index has + * "signal" but is not a signal object we put onto the object. If this + * is our "signal object" then we don't want to remove it. + * @param doc The source index doc to a signal. + */ +export const removeClashes = (doc: BaseSignalHit): BaseSignalHit => { + const { signal, ...noSignal } = doc._source; + if (signal == null || isEventTypeSignal(doc)) { + return doc; + } else { + return { + ...doc, + _source: { ...noSignal }, + }; + } +}; + /** * Builds the `signal.*` fields that are common across all signals. * @param docs The parent signals/events of the new signal to be built. * @param rule The rule that is generating the new signal. */ export const buildSignal = (docs: BaseSignalHit[], rule: RulesSchema): Signal => { - const parents = docs.map(buildParent); + const removedClashes = docs.map(removeClashes); + const parents = removedClashes.map(buildParent); const depth = parents.reduce((acc, parent) => Math.max(parent.depth, acc), 0) + 1; - const ancestors = docs.reduce((acc: Ancestor[], doc) => acc.concat(buildAncestors(doc)), []); + const ancestors = removedClashes.reduce( + (acc: Ancestor[], doc) => acc.concat(buildAncestors(doc)), + [] + ); return { parents, ancestors, @@ -72,9 +95,11 @@ export const buildSignal = (docs: BaseSignalHit[], rule: RulesSchema): Signal => */ export const additionalSignalFields = (doc: BaseSignalHit) => { return { - parent: buildParent(doc), + parent: buildParent(removeClashes(doc)), original_time: doc._source['@timestamp'], original_event: doc._source.event ?? undefined, threshold_count: doc._source.threshold_count ?? undefined, + original_signal: + doc._source.signal != null && !isEventTypeSignal(doc) ? doc._source.signal : undefined, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts index b7cc13fd13a01b..eeeda6561892d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -330,6 +330,18 @@ describe('singleBulkCreate', () => { ]); }); + test('filter duplicate rules does not attempt filters when the signal is not an event type of signal but rather a "clash" from the source index having its own numeric signal type', () => { + const doc = { ...sampleDocWithAncestors(), _source: { signal: 1234 } }; + const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', doc); + expect(filtered).toEqual([]); + }); + + test('filter duplicate rules does not attempt filters when the signal is not an event type of signal but rather a "clash" from the source index having its own object signal type', () => { + const doc = { ...sampleDocWithAncestors(), _source: { signal: {} } }; + const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', doc); + expect(filtered).toEqual([]); + }); + test('create successful and returns proper createdItemsCount', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockResolvedValue(sampleBulkCreateDuplicateResult); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts index 759890cc9d0746..d8889dcfcf4714 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts @@ -14,6 +14,7 @@ import { generateId, makeFloatString, errorAggregator } from './utils'; import { buildBulkBody } from './build_bulk_body'; import { BuildRuleMessage } from './rule_messages'; import { Logger } from '../../../../../../../src/core/server'; +import { isEventTypeSignal } from './build_event_type_signal'; interface SingleBulkCreateParams { filteredEvents: SignalSearchResponse; @@ -50,7 +51,7 @@ export const filterDuplicateRules = ( signalSearchResponse: SignalSearchResponse ) => { return signalSearchResponse.hits.hits.filter((doc) => { - if (doc._source.signal == null) { + if (doc._source.signal == null || !isEventTypeSignal(doc)) { return true; } else { return !( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 9d4e7d8a810515..7128feb80ab3c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -157,6 +157,7 @@ export interface Signal { original_event?: SearchTypes; status: Status; threshold_count?: SearchTypes; + original_signal?: SearchTypes; depth: number; } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts new file mode 100644 index 00000000000000..0cd1c21447dfee --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -0,0 +1,508 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; +import { DEFAULT_SIGNALS_INDEX } from '../../../../plugins/security_solution/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createRule, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getAllSignals, + getSignalsByRuleIds, + getSimpleRule, + waitForSignalsToBePresent, +} from '../../utils'; + +/** + * Specific _id to use for some of the tests. If the archiver changes and you see errors + * here, update this to a new value of a chosen auditbeat record and update the tests values. + */ +export const ID = 'BhbXBmkBR346wHgn4PeZ'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Generating signals from source indexes', () => { + beforeEach(async () => { + await deleteAllAlerts(es); + await createSignalsIndex(supertest); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + }); + + describe('Signals from audit beat are of the expected structure', () => { + beforeEach(async () => { + await esArchiver.load('auditbeat/hosts'); + }); + + afterEach(async () => { + await esArchiver.unload('auditbeat/hosts'); + }); + + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits.length).greaterThan(0); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + ancestors: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + status: 'open', + depth: 1, + parent: { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + original_time: '2019-02-19T17:40:03.790Z', + original_event: { + action: 'socket_closed', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + // create a 1 signal from 1 auditbeat record + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + + // Run signals on top of that 1 signal which should create a single signal (on top of) a signal + const ruleForSignals: CreateRulesSchema = { + ...getSimpleRule(), + rule_id: 'signal-on-signal', + index: [`${DEFAULT_SIGNALS_INDEX}*`], + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + }; + await createRule(supertest, ruleForSignals); + await waitForSignalsToBePresent(supertest, 2); + + // Get our single signal on top of a signal + const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it + id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + ancestors: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + { + rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it + id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + status: 'open', + depth: 2, + parent: { + rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it + id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + original_time: signalNoRule.original_time, // original_time will always be changing sine it's based on a signal created here, so skip testing it + original_event: { + action: 'socket_closed', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + }); + }); + }); + + /** + * These are a set of tests for whenever someone sets up their source + * index to have a name and mapping clash against "signal" with a numeric value. + * You should see the "signal" name/clash being copied to "original_signal" + * underneath the signal object and no errors when they do have a clash. + */ + describe('Signals generated from name clashes', () => { + beforeEach(async () => { + await esArchiver.load('signals/numeric_name_clash'); + }); + + afterEach(async () => { + await esArchiver.unload('signals/numeric_name_clash'); + }); + + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits.length).greaterThan(0); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + ], + status: 'open', + depth: 1, + parent: { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + original_time: '2020-10-28T05:08:53.000Z', + original_signal: 1, + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + // create a 1 signal from 1 auditbeat record + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: `_id:1`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + + // Run signals on top of that 1 signal which should create a single signal (on top of) a signal + const ruleForSignals: CreateRulesSchema = { + ...getSimpleRule(), + rule_id: 'signal-on-signal', + index: [`${DEFAULT_SIGNALS_INDEX}*`], + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + }; + await createRule(supertest, ruleForSignals); + await waitForSignalsToBePresent(supertest, 2); + + // Get our single signal on top of a signal + const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + + expect(signalNoRule).eql({ + parents: [ + { + rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it + id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + { + rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it + id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + status: 'open', + depth: 2, + parent: { + rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it + id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + original_time: signalNoRule.original_time, // original_time will always be changing sine it's based on a signal created here, so skip testing it + original_event: { + kind: 'signal', + }, + }); + }); + }); + + /** + * These are a set of tests for whenever someone sets up their source + * index to have a name and mapping clash against "signal" with an object value. + * You should see the "signal" object/clash being copied to "original_signal" underneath + * the signal object and no errors when they do have a clash. + */ + describe('Signals generated from name clashes', () => { + beforeEach(async () => { + await esArchiver.load('signals/object_clash'); + }); + + afterEach(async () => { + await esArchiver.unload('signals/object_clash'); + }); + + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits.length).greaterThan(0); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + ], + status: 'open', + depth: 1, + parent: { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + original_time: '2020-10-28T05:08:53.000Z', + original_signal: { + child_1: { + child_2: { + value: 'some_value', + }, + }, + }, + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + // create a 1 signal from 1 auditbeat record + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: `_id:1`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + + // Run signals on top of that 1 signal which should create a single signal (on top of) a signal + const ruleForSignals: CreateRulesSchema = { + ...getSimpleRule(), + rule_id: 'signal-on-signal', + index: [`${DEFAULT_SIGNALS_INDEX}*`], + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + }; + await createRule(supertest, ruleForSignals); + await waitForSignalsToBePresent(supertest, 2); + + // Get our single signal on top of a signal + const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + + expect(signalNoRule).eql({ + parents: [ + { + rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it + id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + { + rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it + id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + status: 'open', + depth: 2, + parent: { + rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it + id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + original_time: signalNoRule.original_time, // original_time will always be changing sine it's based on a signal created here, so skip testing it + original_event: { + kind: 'signal', + }, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 24b76853164f22..962ae53b1241f0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -22,6 +22,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_statuses')); + loadTestFile(require.resolve('./generating_signals')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); loadTestFile(require.resolve('./import_rules')); loadTestFile(require.resolve('./read_rules')); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 05a0f73dd0dc4f..c5e417c710283f 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -143,6 +143,19 @@ export const getQuerySignalIds = (signalIds: SignalIds) => ({ }, }); +/** + * Given an array of ruleIds for a test this will get the signals + * created from that rule_id. + * @param ruleIds The rule_id to search for signals + */ +export const getQuerySignalsRuleId = (ruleIds: string[]) => ({ + query: { + terms: { + 'signal.rule.rule_id': ruleIds, + }, + }, +}); + export const setSignalStatus = ({ signalIds, status, @@ -834,6 +847,22 @@ export const getAllSignals = async ( return signalsOpen; }; +export const getSignalsByRuleIds = async ( + supertest: SuperTest, + ruleIds: string[] +): Promise< + SearchResponse<{ + signal: Signal; + }> +> => { + const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalsRuleId(ruleIds)) + .expect(200); + return signalsOpen; +}; + export const installPrePackagedRules = async ( supertest: SuperTest ): Promise => { diff --git a/x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json b/x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json new file mode 100644 index 00000000000000..ca2cf0de2d8452 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json @@ -0,0 +1,12 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "signal_name_clash", + "source": { + "@timestamp": "2020-10-28T05:08:53.000Z", + "signal": 1 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json b/x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json new file mode 100644 index 00000000000000..98823cb4b4250e --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "signal_name_clash", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "signal": { "type": "keyword" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/signals/object_clash/data.json b/x-pack/test/functional/es_archives/signals/object_clash/data.json new file mode 100644 index 00000000000000..d2f844312e8fb2 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/object_clash/data.json @@ -0,0 +1,12 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "signal_object_clash", + "source": { + "@timestamp": "2020-10-28T05:08:53.000Z", + "signal": { "child_1": { "child_2": { "value": "some_value" } } } + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/signals/object_clash/mappings.json b/x-pack/test/functional/es_archives/signals/object_clash/mappings.json new file mode 100644 index 00000000000000..9297ff3e867c9a --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/object_clash/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "signal_object_clash", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "signal": { "type": "object" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} From 03ee1a647695e79c8385d5aa2771ade5ed391803 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Fri, 6 Nov 2020 09:54:37 -0500 Subject: [PATCH 28/81] [Lens] Fix bug in terms formatting (#82776) --- .../operations/definitions/terms/index.tsx | 13 +++++++ .../definitions/terms/terms.test.tsx | 34 ++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 85deb2bac25cae..dcb4646816e135 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -34,6 +34,13 @@ export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { size: number; orderBy: { type: 'alphabetical' } | { type: 'column'; columnId: string }; orderDirection: 'asc' | 'desc'; + // Terms on numeric fields can be formatted + format?: { + id: string; + params?: { + decimals: number; + }; + }; }; } @@ -105,10 +112,16 @@ export const termsOperation: OperationDefinition { + const newParams = { ...oldColumn.params }; + if ('format' in newParams && field.type !== 'number') { + delete newParams.format; + } return { ...oldColumn, + dataType: field.type as DataType, label: ofName(field.displayName), sourceField: field.name, + params: newParams, }; }, onOtherColumnChanged: (currentColumn, columns) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 2c4e67ef0d9b99..1341ca0587c754 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -103,14 +103,40 @@ describe('terms', () => { }, }; const indexPattern = createMockedIndexPattern(); - const newDateField = indexPattern.fields.find((i) => i.name === 'dest')!; + const newNumberField = indexPattern.fields.find((i) => i.name === 'bytes')!; - const column = termsOperation.onFieldChange(oldColumn, indexPattern, newDateField); - expect(column).toHaveProperty('sourceField', 'dest'); + const column = termsOperation.onFieldChange(oldColumn, indexPattern, newNumberField); + expect(column).toHaveProperty('dataType', 'number'); + expect(column).toHaveProperty('sourceField', 'bytes'); expect(column).toHaveProperty('params.size', 5); expect(column).toHaveProperty('params.orderBy.type', 'alphabetical'); expect(column).toHaveProperty('params.orderDirection', 'asc'); - expect(column.label).toContain('dest'); + expect(column.label).toContain('bytes'); + }); + + it('should remove numeric parameters when changing away from number', () => { + const oldColumn: TermsIndexPatternColumn = { + operationType: 'terms', + sourceField: 'bytes', + label: 'Top values of bytes', + isBucketed: true, + dataType: 'number', + params: { + size: 5, + orderBy: { + type: 'alphabetical', + }, + orderDirection: 'asc', + format: { id: 'number', params: { decimals: 0 } }, + }, + }; + const indexPattern = createMockedIndexPattern(); + const newStringField = indexPattern.fields.find((i) => i.name === 'source')!; + + const column = termsOperation.onFieldChange(oldColumn, indexPattern, newStringField); + expect(column).toHaveProperty('dataType', 'string'); + expect(column).toHaveProperty('sourceField', 'source'); + expect(column.params.format).toBeUndefined(); }); }); From d3d3fa7bd2aa12308c90da76439aac3b78d2efde Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Fri, 6 Nov 2020 16:34:30 +0100 Subject: [PATCH 29/81] [Lens] New value labels config option for bar charts (#81776) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../shared_components/toolbar_popover.tsx | 2 +- .../__snapshots__/expression.test.tsx.snap | 72 ++++++++ .../__snapshots__/to_expression.test.ts.snap | 3 + .../xy_visualization/expression.test.tsx | 4 + .../public/xy_visualization/expression.tsx | 79 ++++++++- .../public/xy_visualization/state_helpers.ts | 23 ++- .../xy_visualization/to_expression.test.ts | 29 ++++ .../public/xy_visualization/to_expression.ts | 8 +- .../lens/public/xy_visualization/types.ts | 8 + .../xy_visualization/visualization.test.ts | 2 + .../public/xy_visualization/visualization.tsx | 1 + .../xy_visualization/xy_config_panel.scss | 2 +- .../xy_visualization/xy_config_panel.test.tsx | 143 ++++++++++++++- .../xy_visualization/xy_config_panel.tsx | 164 ++++++++++++++---- .../xy_visualization/xy_suggestions.test.ts | 12 ++ .../public/xy_visualization/xy_suggestions.ts | 1 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../test/functional/page_objects/lens_page.ts | 4 +- 19 files changed, 502 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx index 679f3a44bb60ed..20837424dc7b59 100644 --- a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx +++ b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx @@ -11,7 +11,7 @@ import { EuiIconLegend } from '../assets/legend'; const typeToIconMap: { [type: string]: string | IconType } = { legend: EuiIconLegend as IconType, - values: 'visText', + values: 'number', }; export interface ToolbarPopoverProps { diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index ca6ca9b2722fd3..365bf8f4d6328a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -279,6 +279,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -334,6 +343,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" @@ -457,6 +475,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -512,6 +539,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" @@ -1019,6 +1055,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -1078,6 +1123,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" @@ -1205,6 +1259,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -1264,6 +1327,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap index b35f915336eeed..982f513ae10198 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap @@ -145,6 +145,9 @@ Object { "title": Array [ "", ], + "valueLabels": Array [ + "hide", + ], "xTitle": Array [ "", ], diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 6c9669dc239ea6..a5d292fdf265ad 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -256,6 +256,7 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({ isVisible: false, position: Position.Top, }, + valueLabels: 'hide', axisTitlesVisibilitySettings: { type: 'lens_xy_axisTitlesVisibilityConfig', x: true, @@ -1867,6 +1868,7 @@ describe('xy_expression', () => { yTitle: '', yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top }, + valueLabels: 'hide', tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, @@ -1952,6 +1954,7 @@ describe('xy_expression', () => { yTitle: '', yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top }, + valueLabels: 'hide', tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, @@ -2023,6 +2026,7 @@ describe('xy_expression', () => { yTitle: '', yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top }, + valueLabels: 'hide', tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 877ddd3c0f27d3..d238e052a7c7fe 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -20,6 +20,10 @@ import { GeometryValue, XYChartSeriesIdentifier, StackMode, + RecursivePartial, + Theme, + VerticalAlignment, + HorizontalAlignment, } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { @@ -131,6 +135,11 @@ export const xyChart: ExpressionFunctionDefinition< defaultMessage: 'Define how missing values are treated', }), }, + valueLabels: { + types: ['string'], + options: ['hide', 'inside'], + help: '', + }, tickLabelsVisibilitySettings: { types: ['lens_xy_tickLabelsConfig'], help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', { @@ -214,6 +223,40 @@ export const getXyChartRenderer = (dependencies: { }, }); +function mergeThemeWithValueLabelsStyling( + theme: RecursivePartial, + valuesLabelMode: string = 'hide', + isHorizontal: boolean +) { + const VALUE_LABELS_MAX_FONTSIZE = 15; + const VALUE_LABELS_MIN_FONTSIZE = 10; + const VALUE_LABELS_VERTICAL_OFFSET = -10; + const VALUE_LABELS_HORIZONTAL_OFFSET = 10; + + if (valuesLabelMode === 'hide') { + return theme; + } + return { + ...theme, + ...{ + barSeriesStyle: { + ...theme.barSeriesStyle, + displayValue: { + fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE }, + fill: { textInverted: true, textBorder: 2 }, + alignment: isHorizontal + ? { + vertical: VerticalAlignment.Middle, + } + : { horizontal: HorizontalAlignment.Center }, + offsetX: isHorizontal ? VALUE_LABELS_HORIZONTAL_OFFSET : 0, + offsetY: isHorizontal ? 0 : VALUE_LABELS_VERTICAL_OFFSET, + }, + }, + }, + }; +} + function getIconForSeriesType(seriesType: SeriesType): IconType { return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty'; } @@ -254,7 +297,7 @@ export function XYChart({ onClickValue, onSelectRange, }: XYChartRenderProps) { - const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args; + const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args; const chartTheme = chartsThemeService.useChartsTheme(); const chartBaseTheme = chartsThemeService.useChartsBaseTheme(); @@ -396,6 +439,16 @@ export function XYChart({ return style; }; + const shouldShowValueLabels = + // No stacked bar charts + filteredLayers.every((layer) => !layer.seriesType.includes('stacked')) && + // No histogram charts + !isHistogramViz; + + const baseThemeWithMaybeValueLabels = !shouldShowValueLabels + ? chartTheme + : mergeThemeWithValueLabelsStyling(chartTheme, valueLabels, shouldRotate); + const colorAssignments = getColorAssignments(args.layers, data, formatFactory); return ( @@ -408,7 +461,7 @@ export function XYChart({ } legendPosition={legend.position} showLegendExtra={false} - theme={chartTheme} + theme={baseThemeWithMaybeValueLabels} baseTheme={chartBaseTheme} tooltip={{ headerFormatter: (d) => safeXAccessorLabelRenderer(d.value), @@ -613,6 +666,10 @@ export function XYChart({ }); } + const yAxis = yAxesConfiguration.find((axisConfiguration) => + axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor) + ); + const seriesProps: SeriesSpec = { splitSeriesAccessors: splitAccessor ? [splitAccessor] : [], stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [], @@ -649,9 +706,7 @@ export function XYChart({ palette.params ); }, - groupId: yAxesConfiguration.find((axisConfiguration) => - axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor) - )?.groupId, + groupId: yAxis?.groupId, enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor) && @@ -723,7 +778,19 @@ export function XYChart({ case 'bar_horizontal': case 'bar_horizontal_stacked': case 'bar_horizontal_percentage_stacked': - return ; + const valueLabelsSettings = { + displayValueSettings: { + // This format double fixes two issues in elastic-chart + // * when rotating the chart, the formatter is not correctly picked + // * in some scenarios value labels are not strings, and this breaks the elastic-chart lib + valueFormatter: (d: unknown) => yAxis?.formatter?.convert(d) || '', + showValueLabel: shouldShowValueLabels && valueLabels !== 'hide', + isAlternatingValueLabel: false, + isValueContainedInElement: true, + hideClippedValue: true, + }, + }; + return ; case 'area_stacked': case 'area_percentage_stacked': return ( diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index 41d18e5199e4c2..bf4ffaa36a8700 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -5,7 +5,8 @@ */ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { SeriesType, visualizationTypes, LayerConfig, YConfig } from './types'; +import { FramePublicAPI } from '../types'; +import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types'; export function isHorizontalSeries(seriesType: SeriesType) { return ( @@ -37,3 +38,23 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => { layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null ); }; + +export function hasHistogramSeries( + layers: ValidLayer[] = [], + datasourceLayers?: FramePublicAPI['datasourceLayers'] +) { + if (!datasourceLayers) { + return false; + } + const validLayers = layers.filter(({ accessors }) => accessors.length); + + return validLayers.some(({ layerId, xAccessor }: ValidLayer) => { + const xAxisOperation = datasourceLayers[layerId].getOperationForColumnId(xAccessor); + return ( + xAxisOperation && + xAxisOperation.isBucketed && + xAxisOperation.scale && + xAxisOperation.scale !== 'ordinal' + ); + }); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index 6148824bfec216..05a4b7f460adb9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -43,6 +43,7 @@ describe('#toExpression', () => { xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'Carry', tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true }, @@ -67,6 +68,7 @@ describe('#toExpression', () => { (xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -87,6 +89,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -113,6 +116,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -136,6 +140,7 @@ describe('#toExpression', () => { xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -156,6 +161,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -191,6 +197,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -217,6 +224,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -238,4 +246,25 @@ describe('#toExpression', () => { yRight: [true], }); }); + + it('should correctly report the valueLabels visibility settings', () => { + const expression = xyVisualization.toExpression( + { + legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'inside', + preferredSeriesType: 'bar', + layers: [ + { + layerId: 'first', + seriesType: 'area', + splitAccessor: 'd', + xAccessor: 'a', + accessors: ['b', 'c'], + }, + ], + }, + frame.datasourceLayers + ) as Ast; + expect(expression.chain[0].arguments.valueLabels[0] as Ast).toEqual('inside'); + }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 904d1541a85ec6..df773146cde4db 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -7,13 +7,9 @@ import { Ast } from '@kbn/interpreter/common'; import { ScaleType } from '@elastic/charts'; import { PaletteRegistry } from 'src/plugins/charts/public'; -import { State, LayerConfig } from './types'; +import { State, ValidLayer, LayerConfig } from './types'; import { OperationMetadata, DatasourcePublicAPI } from '../types'; -interface ValidLayer extends LayerConfig { - xAccessor: NonNullable; -} - export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => { const originalOrder = datasource .getTableSpec() @@ -60,6 +56,7 @@ export function toPreviewExpression( ...state.legend, isVisible: false, }, + valueLabels: 'hide', }, datasourceLayers, paletteService, @@ -197,6 +194,7 @@ export const buildExpression = ( ], }, ], + valueLabels: [state?.valueLabels || 'hide'], layers: validLayers.map((layer) => { const columnToLabel: Record = {}; diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index d1e78aec57998d..d21ac675d0745a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -364,6 +364,8 @@ export type SeriesType = export type YAxisMode = 'auto' | 'left' | 'right'; +export type ValueLabelConfig = 'hide' | 'inside' | 'outside'; + export interface YConfig { forAccessor: string; axisMode?: YAxisMode; @@ -381,6 +383,10 @@ export interface LayerConfig { palette?: PaletteOutput; } +export interface ValidLayer extends LayerConfig { + xAccessor: NonNullable; +} + export type LayerArgs = LayerConfig & { columnToLabel?: string; // Actually a JSON key-value pair yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; @@ -398,6 +404,7 @@ export interface XYArgs { yTitle: string; yRightTitle: string; legend: LegendConfig & { type: 'lens_xy_legendConfig' }; + valueLabels: ValueLabelConfig; layers: LayerArgs[]; fittingFunction?: FittingFunction; axisTitlesVisibilitySettings?: AxesSettingsConfig & { @@ -411,6 +418,7 @@ export interface XYArgs { export interface XYState { preferredSeriesType: SeriesType; legend: LegendConfig; + valueLabels?: ValueLabelConfig; fittingFunction?: FittingFunction; layers: LayerConfig[]; xTitle?: string; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 7c49afa53af3ea..5127e5c2c2597a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -15,6 +15,7 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks' function exampleState(): State { return { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -150,6 +151,7 @@ describe('xy_visualization', () => { }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", + "valueLabels": "hide", } `); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index c7f775586ca0da..7e155de14a39ab 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -145,6 +145,7 @@ export const getXyVisualization = ({ state || { title: 'Empty XY chart', legend: { isVisible: true, position: Position.Right }, + valueLabels: 'hide', preferredSeriesType: defaultSeriesType, layers: [ { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss index 5b14fca78e65d8..b9ff6a56d8e35e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss @@ -1,3 +1,3 @@ .lnsXyToolbar__popover { width: 320px; -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx index 2114d63fcfacd4..721bff8684a195 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx @@ -21,6 +21,7 @@ describe('XY Config panels', () => { function testState(): State { return { legend: { isVisible: true, position: Position.Right }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -115,8 +116,9 @@ describe('XY Config panels', () => { expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry'); }); - it('should disable the popover if there is no area or line series', () => { + it('should show currently selected value labels display setting', () => { const state = testState(); + const component = shallow( { ...state, layers: [{ ...state.layers[0], seriesType: 'bar' }], fittingFunction: 'Carry', + valueLabels: 'inside', + }} + /> + ); + + expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside'); + }); + + it('should disable the popover for stacked bar charts', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should disable the popover for percentage area charts', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should disabled the popover if there is histogram series', () => { + // make it detect an histogram series + frame.datasourceLayers.first.getOperationForColumnId = jest.fn().mockReturnValueOnce({ + isBucketed: true, + scale: 'interval', + }); + const state = testState(); + const component = shallow( + + ); + + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should show the popover and display field enabled for bar and horizontal_bar series', () => { + const state = testState(); + + const component = shallow( + + ); + + expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true); + }); + + it('should hide the fitting option for bar series', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(false); + }); + + it('should hide in the popover the display option for area and line series', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false); + }); + + it('should keep the display option for bar series with multiple layers', () => { + frame.datasourceLayers = { + ...frame.datasourceLayers, + second: createMockDatasource('test').publicAPIMock, + }; + + const state = testState(); + const component = shallow( + ); - expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true); + expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true); }); it('should disable the popover if there is no right axis', () => { 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 97e42113fc180e..a22530c5743b44 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 @@ -27,8 +27,20 @@ import { VisualizationToolbarProps, VisualizationDimensionEditorProps, } from '../types'; -import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } from './types'; -import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers'; +import { + State, + SeriesType, + visualizationTypes, + YAxisMode, + AxesSettingsConfig, + ValidLayer, +} from './types'; +import { + isHorizontalChart, + isHorizontalSeries, + getSeriesColor, + hasHistogramSeries, +} from './state_helpers'; import { trackUiEvent } from '../lens_ui_telemetry'; import { fittingFunctionDefinitions } from './fitting_functions'; import { ToolbarPopover, LegendSettingsPopover } from '../shared_components'; @@ -74,6 +86,27 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: }, ]; +const valueLabelsOptions: Array<{ + id: string; + value: 'hide' | 'inside' | 'outside'; + label: string; +}> = [ + { + id: `value_labels_hide`, + value: 'hide', + label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.auto', { + defaultMessage: 'Hide', + }), + }, + { + id: `value_labels_inside`, + value: 'inside', + label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.inside', { + defaultMessage: 'Show', + }), + }, +]; + export function LayerContextMenu(props: VisualizationLayerWidgetProps) { const { state, layerId } = props; const horizontalOnly = isHorizontalChart(state.layers); @@ -118,12 +151,24 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) { } export function XyToolbar(props: VisualizationToolbarProps) { - const { state, setState } = props; + const { state, setState, frame } = props; const hasNonBarSeries = state?.layers.some(({ seriesType }) => ['area_stacked', 'area', 'line'].includes(seriesType) ); + const hasBarNotStacked = state?.layers.some(({ seriesType }) => + ['bar', 'bar_horizontal'].includes(seriesType) + ); + + const isAreaPercentage = state?.layers.some( + ({ seriesType }) => seriesType === 'area_percentage_stacked' + ); + + const isHistogramSeries = Boolean( + hasHistogramSeries(state?.layers as ValidLayer[], frame.datasourceLayers) + ); + const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false; const axisGroups = getAxesConfiguration(state?.layers, shouldRotate); @@ -191,54 +236,99 @@ export function XyToolbar(props: VisualizationToolbarProps) { : !state?.legend.isVisible ? 'hide' : 'show'; + + const valueLabelsVisibilityMode = state?.valueLabels || 'hide'; + + const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries; + const isFittingEnabled = hasNonBarSeries; + return ( - - { - return { - value: id, - dropdownDisplay: ( - <> - {title} - -

{description}

-
- - ), - inputDisplay: title, - }; + {isValueLabelsEnabled ? ( + + {i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', { + defaultMessage: 'Labels', + })} + + } + > + value === valueLabelsVisibilityMode)! + .id + } + onChange={(modeId) => { + const newMode = valueLabelsOptions.find(({ id }) => id === modeId)!.value; + setState({ ...state, valueLabels: newMode }); + }} + /> + + ) : null} + {isFittingEnabled ? ( + setState({ ...state, fittingFunction: value })} - itemLayoutAlign="top" - hasDividers - /> - + > + { + return { + value: id, + dropdownDisplay: ( + <> + {title} + +

{description}

+
+ + ), + inputDisplay: title, + }; + })} + valueOfSelected={state?.fittingFunction || 'None'} + onChange={(value) => setState({ ...state, fittingFunction: value })} + itemLayoutAlign="top" + hasDividers + /> +
+ ) : null}
{ keptLayerIds: [], state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -249,6 +250,7 @@ describe('xy_suggestions', () => { keptLayerIds: ['first'], state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -289,6 +291,7 @@ describe('xy_suggestions', () => { keptLayerIds: ['first', 'second'], state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -523,6 +526,7 @@ describe('xy_suggestions', () => { }, state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -575,6 +579,7 @@ describe('xy_suggestions', () => { test('keeps existing seriesType for initial tables', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', fittingFunction: 'None', preferredSeriesType: 'line', layers: [ @@ -608,6 +613,7 @@ describe('xy_suggestions', () => { test('makes a visible seriesType suggestion for unchanged table without split', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -648,6 +654,7 @@ describe('xy_suggestions', () => { test('suggests seriesType and stacking when there is a split', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -693,6 +700,7 @@ describe('xy_suggestions', () => { (generateId as jest.Mock).mockReturnValueOnce('dummyCol'); const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', fittingFunction: 'None', preferredSeriesType: 'bar', layers: [ @@ -725,6 +733,7 @@ describe('xy_suggestions', () => { test('suggests stacking for unchanged table that has a split', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', layers: [ @@ -760,6 +769,7 @@ describe('xy_suggestions', () => { test('keeps column to dimension mappings on extended tables', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -802,6 +812,7 @@ describe('xy_suggestions', () => { test('changes column mappings when suggestion is reorder', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -845,6 +856,7 @@ describe('xy_suggestions', () => { (generateId as jest.Mock).mockReturnValueOnce('dummyCol'); const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index edb7c4ed522433..7bbb0395773062 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -509,6 +509,7 @@ function buildSuggestion({ const state: State = { legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right }, + valueLabels: currentState?.valueLabels || 'hide', fittingFunction: currentState?.fittingFunction || 'None', xTitle: currentState?.xTitle, yTitle: currentState?.yTitle, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6548fb9752d445..b70420e4338af8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10937,7 +10937,6 @@ "xpack.lens.xyChart.chartTypeLabel": "チャートタイプ", "xpack.lens.xyChart.chartTypeLegend": "チャートタイプ", "xpack.lens.xyChart.emptyXLabel": "(空)", - "xpack.lens.xyChart.fittingDisabledHelpText": "この設定は折れ線グラフとエリアグラフでのみ適用されます。", "xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義", "xpack.lens.xyChart.Gridlines": "グリッド線", "xpack.lens.xyChart.gridlinesSettings.help": "xおよびy軸のグリッド線を表示", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1cb1496cd9a06f..14b29468d843cf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10950,7 +10950,6 @@ "xpack.lens.xyChart.chartTypeLabel": "图表类型", "xpack.lens.xyChart.chartTypeLegend": "图表类型", "xpack.lens.xyChart.emptyXLabel": "(空)", - "xpack.lens.xyChart.fittingDisabledHelpText": "此设置仅适用于折线图和面积图。", "xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式", "xpack.lens.xyChart.Gridlines": "网格线", "xpack.lens.xyChart.gridlinesSettings.help": "显示 x 和 y 轴网格线", diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index ffb74837e9fdd0..3eed3fc0f26ae7 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -222,8 +222,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, async editMissingValues(option: string) { await retry.try(async () => { - await testSubjects.click('lnsMissingValuesButton'); - await testSubjects.exists('lnsMissingValuesSelect'); + await testSubjects.click('lnsValuesButton'); + await testSubjects.exists('lnsValuesButton'); }); await testSubjects.click('lnsMissingValuesSelect'); const optionSelector = await find.byCssSelector(`#${option}`); From 5710f6763b69762bd79c0f3ecdc46fc7e3179627 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 6 Nov 2020 18:03:44 +0200 Subject: [PATCH 30/81] [Visualize] New visualization wizard (#79627) * [Visualizations] New vis wizard * Update functional tests * Create oss plugins for maps and lens and unregister alias function * Add new plugins to .i18nrc.json * Add readme and codeowners to the new plugins * update docs * fix tests * fix types * fixes * Update development docs * fix oss functional tests * Fix jest and x-pack functional tests * Fix functional test * changes on the layout * Cleanup and responsiveness * cleanup unecessary code * add common folder to the new OSS plugins * remove unecessary translations * Update limits.yml file * Fix basic label * Add experimental badge on controls vis * Nice improvements * fixes * Improving styles * Making modal go full height on smaller screens * Fixing sass lint warning * fix lint error * fix internationalization error * PR fixes * PR changes * Use useCallback where possible * Remove translations that need to be translated again * Lazy Load wizard modal * Remove legacyMapVisualizationWarning * Import the OSS plugins constants from the plugins * Export constant from lensOss * Change the new oss plugins from OSS to Oss * Add a new line to the kibana.json files of the new plugins * New nit fix * Fix spaces * Change the texts for the first step of the modal * Fix test * Fixes some of the PR comments * Add onClick funtionality to the entire aggregation based card * Cards description changes, introduce a copyFromRoot method to solve the problem of when disabling the x-pack plugic, to also disable the oss * Create new FTR for testing the functionality of the wizard when both maps and lens apps are disabled * fix eslint error * Change groupTitles and descriptions * Change input vis description * Remove the copyFromRoot from the signature of the ConfigDeprecationFactory and export it from the main entrypoint * Make the disabled cards badge clickable * Changes from code review * Fix functional tests failures * Rename groupTitle to titleInWizard to be more specific * Change vega vis note * minor design changes * fix problem with plugins list docs * Retrieve maps and lens landing page from docs service and add tracking url param * Fix funtional test for the new dashboard flow * Fix logic in alias registry for removing the discardOnRegister alias * no need to remove the alias entry from the discardOnRegister array Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: miukimiu --- .github/CODEOWNERS | 1 + .i18nrc.json | 2 + .sass-lint.yml | 1 + docs/developer/plugin-list.asciidoc | 10 + ...-plugin-core-public.doclinksstart.links.md | 1 + ...kibana-plugin-core-public.doclinksstart.md | 2 +- .../deprecation/deprecation_factory.test.ts | 85 +- .../src/deprecation/deprecation_factory.ts | 26 + packages/kbn-config/src/deprecation/index.ts | 2 +- packages/kbn-config/src/index.ts | 1 + packages/kbn-optimizer/limits.yml | 2 + .../public/doc_links/doc_links_service.ts | 4 + src/core/public/public.api.md | 1 + .../public/input_control_vis_type.ts | 6 +- src/plugins/lens_oss/README.md | 6 + src/plugins/lens_oss/common/constants.ts | 22 + .../common}/index.ts | 2 +- src/plugins/lens_oss/config.ts | 26 + src/plugins/lens_oss/kibana.json | 10 + src/plugins/lens_oss/public/index.ts | 21 + .../public/plugin.ts} | 48 +- src/plugins/lens_oss/public/vis_type_alias.ts | 48 + .../server/index.ts} | 31 +- src/plugins/maps_oss/README.md | 6 + src/plugins/maps_oss/common/constants.ts | 22 + src/plugins/maps_oss/common/index.ts | 20 + src/plugins/maps_oss/config.ts | 26 + src/plugins/maps_oss/kibana.json | 10 + src/plugins/maps_oss/public/index.ts | 21 + src/plugins/maps_oss/public/plugin.ts | 43 + src/plugins/maps_oss/public/vis_type_alias.ts | 44 + src/plugins/maps_oss/server/index.ts | 32 + .../vis_type_markdown/public/markdown_vis.ts | 7 +- .../public/metrics_type.ts | 5 +- src/plugins/vis_type_vega/public/vega_type.ts | 11 +- src/plugins/vis_type_vislib/public/gauge.ts | 3 +- src/plugins/visualizations/public/index.scss | 1 - src/plugins/visualizations/public/index.ts | 1 + src/plugins/visualizations/public/mocks.ts | 2 + src/plugins/visualizations/public/plugin.ts | 2 + src/plugins/visualizations/public/services.ts | 3 + .../public/vis_types/base_vis_type.ts | 17 +- .../visualizations/public/vis_types/index.ts | 1 + .../visualizations/public/vis_types/types.ts | 42 + .../public/vis_types/types_service.ts | 17 +- .../vis_types/vis_type_alias_registry.ts | 31 +- .../__snapshots__/new_vis_modal.test.tsx.snap | 2141 ----------------- .../visualizations/public/wizard/_index.scss | 1 - .../agg_based_selection.test.tsx | 122 + .../agg_based_selection.tsx | 163 ++ .../wizard/agg_based_selection/index.ts | 20 + .../wizard/{_dialog.scss => dialog.scss} | 107 +- .../public/wizard/dialog_navigation.tsx | 48 + .../group_selection/group_selection.scss | 30 + .../group_selection/group_selection.test.tsx | 321 +++ .../group_selection/group_selection.tsx | 294 +++ .../public/wizard/group_selection/index.ts | 20 + .../public/wizard/new_vis_modal.test.tsx | 143 +- .../public/wizard/new_vis_modal.tsx | 30 +- .../search_selection/search_selection.tsx | 5 +- .../public/wizard/show_new_vis.tsx | 45 +- .../type_selection/new_vis_help.test.tsx | 73 - .../wizard/type_selection/new_vis_help.tsx | 57 - .../wizard/type_selection/type_selection.tsx | 289 --- .../dashboard/create_and_add_embeddables.js | 4 +- test/functional/apps/dashboard/view_edit.js | 1 + .../apps/getting_started/_shakespeare.js | 2 +- test/functional/apps/visualize/_area_chart.js | 6 +- .../functional/apps/visualize/_chart_types.ts | 19 +- test/functional/apps/visualize/_data_table.js | 20 +- .../visualize/_data_table_nontimeindex.js | 6 +- .../_data_table_notimeindex_filters.ts | 2 +- .../apps/visualize/_embedding_chart.js | 2 +- .../apps/visualize/_experimental_vis.js | 2 +- .../functional/apps/visualize/_gauge_chart.js | 2 +- .../apps/visualize/_heatmap_chart.js | 2 +- .../visualize/_histogram_request_start.js | 2 +- test/functional/apps/visualize/_inspector.js | 2 +- test/functional/apps/visualize/_line_chart.js | 4 +- .../apps/visualize/_linked_saved_searches.ts | 2 +- .../apps/visualize/_metric_chart.js | 2 +- test/functional/apps/visualize/_pie_chart.js | 12 +- .../apps/visualize/_point_series_options.js | 6 +- test/functional/apps/visualize/_region_map.js | 2 +- test/functional/apps/visualize/_tag_cloud.js | 2 +- test/functional/apps/visualize/_tile_map.js | 6 +- .../apps/visualize/_vertical_bar_chart.js | 6 +- .../_vertical_bar_chart_nontimeindex.js | 2 +- .../functional/page_objects/visualize_page.ts | 40 + .../services/dashboard/visualizations.ts | 1 + .../self_changing_vis.js | 2 +- x-pack/plugins/lens/kibana.json | 2 +- x-pack/plugins/lens/public/plugin.ts | 6 +- x-pack/plugins/lens/public/vis_type_alias.ts | 15 +- x-pack/plugins/maps/kibana.json | 2 +- .../maps/public/maps_vis_type_alias.js | 12 +- x-pack/plugins/maps/public/plugin.ts | 9 +- .../translations/translations/ja-JP.json | 14 - .../translations/translations/zh-CN.json | 14 - x-pack/scripts/functional_tests.js | 1 + .../apps/lens/persistent_context.ts | 2 +- .../apps/maps/visualize_create_menu.js | 2 + .../functional/apps/security/rbac_phase1.js | 2 +- .../apps/visualize/precalculated_histogram.ts | 2 +- .../functional/apps/visualize/reporting.ts | 1 + .../test/functional_vis_wizard/apps/index.ts | 14 + .../apps/visualization_wizard.ts | 34 + x-pack/test/functional_vis_wizard/config.ts | 28 + .../ftr_provider_context.d.ts | 12 + 109 files changed, 2056 insertions(+), 2909 deletions(-) create mode 100644 src/plugins/lens_oss/README.md create mode 100644 src/plugins/lens_oss/common/constants.ts rename src/plugins/{visualizations/public/wizard/type_selection => lens_oss/common}/index.ts (94%) create mode 100644 src/plugins/lens_oss/config.ts create mode 100644 src/plugins/lens_oss/kibana.json create mode 100644 src/plugins/lens_oss/public/index.ts rename src/plugins/{visualizations/public/wizard/type_selection/vis_help_text.tsx => lens_oss/public/plugin.ts} (53%) create mode 100644 src/plugins/lens_oss/public/vis_type_alias.ts rename src/plugins/{visualizations/public/wizard/type_selection/vis_type_icon.tsx => lens_oss/server/index.ts} (54%) create mode 100644 src/plugins/maps_oss/README.md create mode 100644 src/plugins/maps_oss/common/constants.ts create mode 100644 src/plugins/maps_oss/common/index.ts create mode 100644 src/plugins/maps_oss/config.ts create mode 100644 src/plugins/maps_oss/kibana.json create mode 100644 src/plugins/maps_oss/public/index.ts create mode 100644 src/plugins/maps_oss/public/plugin.ts create mode 100644 src/plugins/maps_oss/public/vis_type_alias.ts create mode 100644 src/plugins/maps_oss/server/index.ts delete mode 100644 src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap delete mode 100644 src/plugins/visualizations/public/wizard/_index.scss create mode 100644 src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx create mode 100644 src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx create mode 100644 src/plugins/visualizations/public/wizard/agg_based_selection/index.ts rename src/plugins/visualizations/public/wizard/{_dialog.scss => dialog.scss} (81%) create mode 100644 src/plugins/visualizations/public/wizard/dialog_navigation.tsx create mode 100644 src/plugins/visualizations/public/wizard/group_selection/group_selection.scss create mode 100644 src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx create mode 100644 src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx create mode 100644 src/plugins/visualizations/public/wizard/group_selection/index.ts delete mode 100644 src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx delete mode 100644 src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx delete mode 100644 src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx create mode 100644 x-pack/test/functional_vis_wizard/apps/index.ts create mode 100644 x-pack/test/functional_vis_wizard/apps/visualization_wizard.ts create mode 100644 x-pack/test/functional_vis_wizard/config.ts create mode 100644 x-pack/test/functional_vis_wizard/ftr_provider_context.d.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 27532f0f377f9a..96670b5d5107b0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,6 +12,7 @@ /src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app +/src/plugins/lens_oss/ @elastic/kibana-app /src/plugins/management/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app diff --git a/.i18nrc.json b/.i18nrc.json index 68e38d3976a68b..653c67b535bff7 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -59,6 +59,8 @@ "visTypeVislib": "src/plugins/vis_type_vislib", "visTypeXy": "src/plugins/vis_type_xy", "visualizations": "src/plugins/visualizations", + "lensOss": "src/plugins/lens_oss", + "mapsOss": "src/plugins/maps_oss", "visualize": "src/plugins/visualize", "apmOss": "src/plugins/apm_oss", "usageCollection": "src/plugins/usage_collection" diff --git a/.sass-lint.yml b/.sass-lint.yml index 9eed50602f5205..85599750b0cb8d 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -6,6 +6,7 @@ files: - 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'src/plugins/vis_type_vega/**/*.s+(a|c)ss' - 'src/plugins/vis_type_xy/**/*.s+(a|c)ss' + - 'src/plugins/visualizations/public/wizard/**/*.s+(a|c)ss' - 'x-pack/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' - 'x-pack/plugins/lens/**/*.s+(a|c)ss' diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b59545cbb85a64..3c62c1fbca9825 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -130,6 +130,11 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |The legacyExport plugin adds support for the legacy saved objects export format. +|{kib-repo}blob/{branch}/src/plugins/lens_oss/README.md[lensOss] +|The lens_oss plugin registers the lens visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + + |{kib-repo}blob/{branch}/src/plugins/management/README.md[management] |This plugins contains the "Stack Management" page framework. It offers navigation and an API to link individual managment section into it. This plugin does not contain any individual @@ -140,6 +145,11 @@ management section itself. |Internal objects used by the Coordinate, Region, and Vega visualizations. +|{kib-repo}blob/{branch}/src/plugins/maps_oss/README.md[mapsOss] +|The maps_oss plugin registers the maps visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + + |{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] |The navigation plugins exports the TopNavMenu component. It also provides a stateful version of it on the start contract. diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 3afd5eaa6f1f7d..9da31bb16b56b6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -9,6 +9,7 @@ ```typescript readonly links: { readonly dashboard: { + readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 5249381969b98d..01504aafe3bae9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
} | | diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index 3910ee3235cafe..64b02f104a1f1f 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -18,7 +18,7 @@ */ import { ConfigDeprecationLogger } from './types'; -import { configDeprecationFactory } from './deprecation_factory'; +import { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; @@ -250,6 +250,89 @@ describe('DeprecationFactory', () => { }); }); + describe('copyFromRoot', () => { + it('copies a property to a different namespace', () => { + const rawConfig = { + originplugin: { + deprecated: 'toberenamed', + valid: 'valid', + }, + destinationplugin: { + property: 'value', + }, + }; + const processed = copyFromRoot('originplugin.deprecated', 'destinationplugin.renamed')( + rawConfig, + 'does-not-matter', + logger + ); + expect(processed).toEqual({ + originplugin: { + deprecated: 'toberenamed', + valid: 'valid', + }, + destinationplugin: { + renamed: 'toberenamed', + property: 'value', + }, + }); + expect(deprecationMessages.length).toEqual(0); + }); + + it('does not alter config if origin property is not present', () => { + const rawConfig = { + myplugin: { + new: 'new', + valid: 'valid', + }, + someOtherPlugin: { + property: 'value', + }, + }; + const processed = copyFromRoot('myplugin.deprecated', 'myplugin.new')( + rawConfig, + 'does-not-matter', + logger + ); + expect(processed).toEqual({ + myplugin: { + new: 'new', + valid: 'valid', + }, + someOtherPlugin: { + property: 'value', + }, + }); + expect(deprecationMessages.length).toEqual(0); + }); + + it('does not alter config if they both exist', () => { + const rawConfig = { + myplugin: { + deprecated: 'deprecated', + renamed: 'renamed', + }, + someOtherPlugin: { + property: 'value', + }, + }; + const processed = copyFromRoot('myplugin.deprecated', 'someOtherPlugin.property')( + rawConfig, + 'does-not-matter', + logger + ); + expect(processed).toEqual({ + myplugin: { + deprecated: 'deprecated', + renamed: 'renamed', + }, + someOtherPlugin: { + property: 'value', + }, + }); + }); + }); + describe('unused', () => { it('removes the unused property from the config and logs a warning is present', () => { const rawConfig = { diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 0598347d2cffcc..70a55fedf05bea 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -56,6 +56,26 @@ const _rename = ( return config; }; +const _copy = ( + config: Record, + rootPath: string, + originKey: string, + destinationKey: string +) => { + const originPath = getPath(rootPath, originKey); + const originValue = get(config, originPath); + if (originValue === undefined) { + return config; + } + + const destinationPath = getPath(rootPath, destinationKey); + const destinationValue = get(config, destinationPath); + if (destinationValue === undefined) { + set(config, destinationPath, originValue); + } + return config; +}; + const _unused = ( config: Record, rootPath: string, @@ -80,6 +100,12 @@ const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): Confi log ) => _rename(config, '', log, oldKey, newKey, silent); +export const copyFromRoot = (originKey: string, destinationKey: string): ConfigDeprecation => ( + config, + rootPath, + log +) => _copy(config, '', originKey, destinationKey); + const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => _unused(config, rootPath, log, unusedKey); diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 504dbfeeb001a4..4609b7b1d62d0d 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -24,5 +24,5 @@ export { ConfigDeprecationFactory, ConfigDeprecationProvider, } from './types'; -export { configDeprecationFactory } from './deprecation_factory'; +export { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 68609c6d5c7c31..b834568e8b3ae7 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -25,6 +25,7 @@ export { ConfigDeprecationLogger, ConfigDeprecationProvider, ConfigDeprecationWithContext, + copyFromRoot, } from './deprecation'; export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 11e977c74cf22f..701b7cab21600d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -45,6 +45,7 @@ pageLoadAssetSize: kibanaReact: 161921 kibanaUtils: 198829 lens: 96624 + lensOss: 19341 licenseManagement: 41817 licensing: 39008 lists: 183665 @@ -53,6 +54,7 @@ pageLoadAssetSize: maps: 183610 mapsLegacy: 116817 mapsLegacyLicensing: 20214 + mapsOss: 19284 ml: 82187 monitoring: 50000 navigation: 37269 diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6988a3211fa12d..48187fe4653922 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -37,6 +37,7 @@ export class DocLinksService { ELASTIC_WEBSITE_URL, links: { dashboard: { + guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#trigger-picker`, urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#templating`, @@ -134,6 +135,8 @@ export class DocLinksService { visualize: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/visualize.html`, timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html#timelion-deprecation`, + lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`, + maps: `${ELASTIC_WEBSITE_URL}maps`, }, }, }); @@ -146,6 +149,7 @@ export interface DocLinksStart { readonly ELASTIC_WEBSITE_URL: string; readonly links: { readonly dashboard: { + readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index fd2d943cab9d29..781a50f849e241 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -460,6 +460,7 @@ export interface DocLinksStart { // (undocumented) readonly links: { readonly dashboard: { + readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts index 782df67f5c58a4..6e33e18c1603b0 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts @@ -18,8 +18,7 @@ */ import { i18n } from '@kbn/i18n'; - -import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; +import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public'; import { createInputControlVisController } from './vis_controller'; import { getControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; @@ -37,8 +36,9 @@ export function createInputControlVisTypeDefinition( defaultMessage: 'Controls', }), icon: 'controlsHorizontal', + group: VisGroups.TOOLS, description: i18n.translate('inputControl.register.controlsDescription', { - defaultMessage: 'Create interactive controls for easy dashboard manipulation.', + defaultMessage: 'Add dropdown menus and range sliders to your dashboard.', }), stage: 'experimental', visualization: InputControlVisController, diff --git a/src/plugins/lens_oss/README.md b/src/plugins/lens_oss/README.md new file mode 100644 index 00000000000000..187da2497026e9 --- /dev/null +++ b/src/plugins/lens_oss/README.md @@ -0,0 +1,6 @@ +# lens_oss + +The lens_oss plugin registers the lens visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + +`visualizations.unregisterAlias('lensOss')` \ No newline at end of file diff --git a/src/plugins/lens_oss/common/constants.ts b/src/plugins/lens_oss/common/constants.ts new file mode 100644 index 00000000000000..ac92c9e1969936 --- /dev/null +++ b/src/plugins/lens_oss/common/constants.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export const APP_NAME = 'lens'; +export const PLUGIN_ID_OSS = 'lensOss'; +export const APP_PATH = '#/'; +export const APP_ICON = 'lensApp'; diff --git a/src/plugins/visualizations/public/wizard/type_selection/index.ts b/src/plugins/lens_oss/common/index.ts similarity index 94% rename from src/plugins/visualizations/public/wizard/type_selection/index.ts rename to src/plugins/lens_oss/common/index.ts index c4093b4dec3e83..fd1c2843d6b26a 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/index.ts +++ b/src/plugins/lens_oss/common/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { TypeSelection } from './type_selection'; +export * from './constants'; diff --git a/src/plugins/lens_oss/config.ts b/src/plugins/lens_oss/config.ts new file mode 100644 index 00000000000000..6749bd83de39f9 --- /dev/null +++ b/src/plugins/lens_oss/config.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/lens_oss/kibana.json b/src/plugins/lens_oss/kibana.json new file mode 100644 index 00000000000000..3e3d3585f37fb8 --- /dev/null +++ b/src/plugins/lens_oss/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "lensOss", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "visualizations" + ], + "extraPublicDirs": ["common/constants"] +} diff --git a/src/plugins/lens_oss/public/index.ts b/src/plugins/lens_oss/public/index.ts new file mode 100644 index 00000000000000..f936052a37264b --- /dev/null +++ b/src/plugins/lens_oss/public/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { LensOSSPlugin } from './plugin'; + +export const plugin = () => new LensOSSPlugin(); diff --git a/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx b/src/plugins/lens_oss/public/plugin.ts similarity index 53% rename from src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx rename to src/plugins/lens_oss/public/plugin.ts index 8517919955f7cc..15b815dab87c87 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx +++ b/src/plugins/lens_oss/public/plugin.ts @@ -16,35 +16,27 @@ * specific language governing permissions and limitations * under the License. */ +import { DocLinksStart, CoreSetup } from 'src/core/public'; +import { VisualizationsSetup } from '../../visualizations/public'; +import { getLensAliasConfig } from './vis_type_alias'; -import React from 'react'; - -import { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +export interface LensPluginSetupDependencies { + visualizations: VisualizationsSetup; +} -interface VisHelpTextProps { - name: string; - title: string; - description?: string; - highlightMsg?: string; +export interface LensPluginStartDependencies { + docLinks: DocLinksStart; } -export const VisHelpText = ({ name, title, description, highlightMsg }: VisHelpTextProps) => { - return ( - - -

{title}

-
- -
- - {highlightMsg && ( -

- {highlightMsg} -

- )} -

{description}

-
-
-
- ); -}; +export class LensOSSPlugin { + setup( + core: CoreSetup, + { visualizations }: LensPluginSetupDependencies + ) { + core.getStartServices().then(([coreStart]) => { + visualizations.registerAlias(getLensAliasConfig(coreStart.docLinks)); + }); + } + + start() {} +} diff --git a/src/plugins/lens_oss/public/vis_type_alias.ts b/src/plugins/lens_oss/public/vis_type_alias.ts new file mode 100644 index 00000000000000..230209646deb55 --- /dev/null +++ b/src/plugins/lens_oss/public/vis_type_alias.ts @@ -0,0 +1,48 @@ +/* + * 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 { VisTypeAlias } from 'src/plugins/visualizations/public'; +import { DocLinksStart } from 'src/core/public'; +import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; + +export const getLensAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ + aliasPath: APP_PATH, + aliasApp: APP_NAME, + name: PLUGIN_ID_OSS, + title: i18n.translate('lensOss.visTypeAlias.title', { + defaultMessage: 'Lens', + }), + description: i18n.translate('lensOss.visTypeAlias.description', { + defaultMessage: + 'Create visualizations with our drag-and-drop editor. Switch between visualization types at any time. Best for most visualizations.', + }), + icon: APP_ICON, + stage: 'production', + disabled: true, + note: i18n.translate('lensOss.visTypeAlias.note', { + defaultMessage: 'Recommended for most users.', + }), + promoTooltip: { + description: i18n.translate('lensOss.visTypeAlias.promoTooltip.description', { + defaultMessage: 'Try Lens for free with Elastic. Learn more.', + }), + link: `${links.visualize.lens}?blade=kibanaossvizwizard`, + }, +}); diff --git a/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx b/src/plugins/lens_oss/server/index.ts similarity index 54% rename from src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx rename to src/plugins/lens_oss/server/index.ts index a9837313f29171..1a089a5382cc15 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx +++ b/src/plugins/lens_oss/server/index.ts @@ -17,25 +17,16 @@ * under the License. */ -import { EuiIcon, IconType } from '@elastic/eui'; -import React from 'react'; +import { PluginConfigDescriptor } from 'kibana/server'; +import { copyFromRoot } from '@kbn/config'; +import { configSchema, ConfigSchema } from '../config'; -interface VisTypeIconProps { - icon?: IconType; - image?: string; -} - -/** - * This renders the icon for a specific visualization type. - * This currently checks the following: - * - If image is set, use that as the `src` of an image - * - Otherwise use the icon as an EuiIcon or the 'empty' icon if that's not set - */ -export const VisTypeIcon = ({ icon, image }: VisTypeIconProps) => { - return ( - - {image && } - {!image && } - - ); +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: () => [copyFromRoot('xpack.lens.enabled', 'lens_oss.enabled')], }; + +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/src/plugins/maps_oss/README.md b/src/plugins/maps_oss/README.md new file mode 100644 index 00000000000000..ed91de500fbfb0 --- /dev/null +++ b/src/plugins/maps_oss/README.md @@ -0,0 +1,6 @@ +# maps_oss + +The maps_oss plugin registers the maps visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + +`visualizations.unregisterAlias('mapsOss')` \ No newline at end of file diff --git a/src/plugins/maps_oss/common/constants.ts b/src/plugins/maps_oss/common/constants.ts new file mode 100644 index 00000000000000..b4063c68840b38 --- /dev/null +++ b/src/plugins/maps_oss/common/constants.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export const APP_NAME = 'maps'; +export const PLUGIN_ID_OSS = 'mapsOss'; +export const APP_PATH = '/map'; +export const APP_ICON = 'gisApp'; diff --git a/src/plugins/maps_oss/common/index.ts b/src/plugins/maps_oss/common/index.ts new file mode 100644 index 00000000000000..fd1c2843d6b26a --- /dev/null +++ b/src/plugins/maps_oss/common/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './constants'; diff --git a/src/plugins/maps_oss/config.ts b/src/plugins/maps_oss/config.ts new file mode 100644 index 00000000000000..6749bd83de39f9 --- /dev/null +++ b/src/plugins/maps_oss/config.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/maps_oss/kibana.json b/src/plugins/maps_oss/kibana.json new file mode 100644 index 00000000000000..19770dcffaadd7 --- /dev/null +++ b/src/plugins/maps_oss/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "mapsOss", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "visualizations" + ], + "extraPublicDirs": ["common/constants"] +} diff --git a/src/plugins/maps_oss/public/index.ts b/src/plugins/maps_oss/public/index.ts new file mode 100644 index 00000000000000..ec18ff1fde638a --- /dev/null +++ b/src/plugins/maps_oss/public/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { MapsOSSPlugin } from './plugin'; + +export const plugin = () => new MapsOSSPlugin(); diff --git a/src/plugins/maps_oss/public/plugin.ts b/src/plugins/maps_oss/public/plugin.ts new file mode 100644 index 00000000000000..3369e0bf277065 --- /dev/null +++ b/src/plugins/maps_oss/public/plugin.ts @@ -0,0 +1,43 @@ +/* + * 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 { DocLinksStart, CoreSetup } from 'src/core/public'; +import { VisualizationsSetup } from '../../visualizations/public'; +import { getMapsAliasConfig } from './vis_type_alias'; + +export interface MapsPluginSetupDependencies { + visualizations: VisualizationsSetup; +} + +export interface MapsPluginStartDependencies { + docLinks: DocLinksStart; +} + +export class MapsOSSPlugin { + setup( + core: CoreSetup, + { visualizations }: MapsPluginSetupDependencies + ) { + core.getStartServices().then(([coreStart]) => { + visualizations.registerAlias(getMapsAliasConfig(coreStart.docLinks)); + }); + } + + start() {} +} diff --git a/src/plugins/maps_oss/public/vis_type_alias.ts b/src/plugins/maps_oss/public/vis_type_alias.ts new file mode 100644 index 00000000000000..14fdc06bc539f7 --- /dev/null +++ b/src/plugins/maps_oss/public/vis_type_alias.ts @@ -0,0 +1,44 @@ +/* + * 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 { VisTypeAlias } from 'src/plugins/visualizations/public'; +import { DocLinksStart } from 'src/core/public'; +import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; + +export const getMapsAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ + aliasPath: APP_PATH, + aliasApp: APP_NAME, + name: PLUGIN_ID_OSS, + title: i18n.translate('mapsOss.visTypeAlias.title', { + defaultMessage: 'Maps', + }), + description: i18n.translate('mapsOss.visTypeAlias.description', { + defaultMessage: 'Plot and style your geo data in a multi layer map.', + }), + icon: APP_ICON, + stage: 'production', + disabled: true, + promoTooltip: { + description: i18n.translate('mapsOss.visTypeAlias.promoTooltip.description', { + defaultMessage: 'Try maps for free with Elastic. Learn more.', + }), + link: `${links.visualize.maps}?blade=kibanaossvizwizard`, + }, +}); diff --git a/src/plugins/maps_oss/server/index.ts b/src/plugins/maps_oss/server/index.ts new file mode 100644 index 00000000000000..defc3a09c25383 --- /dev/null +++ b/src/plugins/maps_oss/server/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { PluginConfigDescriptor } from 'kibana/server'; +import { copyFromRoot } from '@kbn/config'; +import { configSchema, ConfigSchema } from '../config'; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: () => [copyFromRoot('xpack.maps.enabled', 'maps_oss.enabled')], +}; + +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/src/plugins/vis_type_markdown/public/markdown_vis.ts b/src/plugins/vis_type_markdown/public/markdown_vis.ts index 27ac038aee6fff..b6ec2cbd993b08 100644 --- a/src/plugins/vis_type_markdown/public/markdown_vis.ts +++ b/src/plugins/vis_type_markdown/public/markdown_vis.ts @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import { MarkdownOptions } from './markdown_options'; import { SettingsOptions } from './settings_options_lazy'; import { DefaultEditorSize } from '../../vis_default_editor/public'; +import { VisGroups } from '../../visualizations/public'; import { toExpressionAst } from './to_ast'; export const markdownVisDefinition = { @@ -29,8 +30,12 @@ export const markdownVisDefinition = { title: 'Markdown', isAccessible: true, icon: 'visText', + group: VisGroups.TOOLS, + titleInWizard: i18n.translate('visTypeMarkdown.markdownTitleInWizard', { + defaultMessage: 'Text', + }), description: i18n.translate('visTypeMarkdown.markdownDescription', { - defaultMessage: 'Create a document using markdown syntax', + defaultMessage: 'Add text and images to your dashboard.', }), toExpressionAst, visConfig: { diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index d6621870fef67e..682517ab1a9968 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -25,15 +25,16 @@ import { EditorController } from './application'; // @ts-ignore import { PANEL_TYPES } from '../common/panel_types'; import { VisEditor } from './application/components/vis_editor_lazy'; -import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; +import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public'; export const metricsVisDefinition = { name: 'metrics', title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }), description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', { - defaultMessage: 'Build time-series using a visual pipeline interface', + defaultMessage: 'Perform advanced analysis of your time series data.', }), icon: 'visVisualBuilder', + group: VisGroups.PROMOTED, visConfig: { defaults: { id: '61ca57f0-469d-11e7-af02-69e470af7417', diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index 17f35b75f00167..2211abb54aa93f 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -25,7 +25,7 @@ import { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; import { getDefaultSpec } from './default_spec'; import { createInspectorAdapters } from './vega_inspector'; -import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; +import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public'; import { toExpressionAst } from './to_ast'; import { VisParams } from './vega_fn'; import { getInfoMessage } from './components/experimental_map_vis_info'; @@ -41,10 +41,17 @@ export const createVegaTypeDefinition = ( title: 'Vega', getInfoMessage, description: i18n.translate('visTypeVega.type.vegaDescription', { - defaultMessage: 'Create custom visualizations using Vega and Vega-Lite', + defaultMessage: 'Use Vega to create new types of visualizations.', description: 'Vega and Vega-Lite are product names and should not be translated', }), + note: i18n.translate('visTypeVega.type.vegaNote', { + defaultMessage: 'Requires knowledge of Vega syntax.', + }), icon: 'visVega', + group: VisGroups.PROMOTED, + titleInWizard: i18n.translate('visTypeVega.type.vegaTitleInWizard', { + defaultMessage: 'Custom visualization', + }), visConfig: { defaults: { spec: getDefaultSpec() } }, editorConfig: { optionsTemplate: VegaVisEditorComponent, diff --git a/src/plugins/vis_type_vislib/public/gauge.ts b/src/plugins/vis_type_vislib/public/gauge.ts index 86e3b8793d618c..2b3c415087ee1e 100644 --- a/src/plugins/vis_type_vislib/public/gauge.ts +++ b/src/plugins/vis_type_vislib/public/gauge.ts @@ -61,8 +61,7 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions = { title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', description: i18n.translate('visTypeVislib.gauge.gaugeDescription', { - defaultMessage: - "Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.", + defaultMessage: 'Gauges indicate the status of a metric.', }), toExpressionAst, visConfig: { diff --git a/src/plugins/visualizations/public/index.scss b/src/plugins/visualizations/public/index.scss index 2b61535f3e7f23..0202419cea2328 100644 --- a/src/plugins/visualizations/public/index.scss +++ b/src/plugins/visualizations/public/index.scss @@ -1,3 +1,2 @@ -@import 'wizard/index'; @import 'embeddable/index'; @import 'components/index'; diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 7bd4466b23166b..d66a6f6113cad7 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -36,6 +36,7 @@ export { getSchemas as getVisSchemas } from './legacy/build_pipeline'; /** @public types */ export { VisualizationsSetup, VisualizationsStart }; +export { VisGroups } from './vis_types'; export type { VisTypeAlias, VisType, BaseVisTypeOptions, ReactVisTypeOptions } from './vis_types'; export { VisParams, SerializedVis, SerializedVisData, VisData } from './vis'; export type VisualizeEmbeddableFactoryContract = PublicContract; diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index f20e87dbd3b6a8..66399352bea7d9 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -41,6 +41,8 @@ const createStartContract = (): VisualizationsStart => ({ get: jest.fn(), all: jest.fn(), getAliases: jest.fn(), + getByGroup: jest.fn(), + unRegisterAlias: jest.fn(), savedVisualizationsLoader: { get: jest.fn(), } as any, diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index c1dbe39def64c8..29e31e92b971e2 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -49,6 +49,7 @@ import { setOverlays, setSavedSearchLoader, setEmbeddable, + setDocLinks, } from './services'; import { VISUALIZE_EMBEDDABLE_TYPE, @@ -172,6 +173,7 @@ export class VisualizationsPlugin setCapabilities(core.application.capabilities); setHttp(core.http); setSavedObjects(core.savedObjects); + setDocLinks(core.docLinks); setIndexPatterns(data.indexPatterns); setSearch(data.search); setFilterManager(data.query.filterManager); diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts index 0761b8862e8e36..08537b4ac30810 100644 --- a/src/plugins/visualizations/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -26,6 +26,7 @@ import { IUiSettingsClient, OverlayStart, SavedObjectsStart, + DocLinksStart, } from '../../../core/public'; import { TypesStart } from './vis_types'; import { createGetterSetter } from '../../../plugins/kibana_utils/public'; @@ -60,6 +61,8 @@ export const [getTypes, setTypes] = createGetterSetter('Types'); export const [getI18n, setI18n] = createGetterSetter('I18n'); +export const [getDocLinks, setDocLinks] = createGetterSetter('DocLinks'); + export const [getFilterManager, setFilterManager] = createGetterSetter( 'FilterManager' ); diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index f2933de723a393..807582723172da 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -20,7 +20,7 @@ import { defaultsDeep } from 'lodash'; import { ISchemas } from 'src/plugins/vis_default_editor/public'; import { VisParams } from '../types'; -import { VisType, VisTypeOptions } from './types'; +import { VisType, VisTypeOptions, VisGroups } from './types'; interface CommonBaseVisTypeOptions extends Pick< @@ -41,7 +41,14 @@ interface CommonBaseVisTypeOptions >, Pick< Partial>, - 'editorConfig' | 'hidden' | 'stage' | 'useCustomNoDataScreen' | 'visConfig' + | 'editorConfig' + | 'hidden' + | 'stage' + | 'useCustomNoDataScreen' + | 'visConfig' + | 'group' + | 'titleInWizard' + | 'note' > { options?: Partial['options']>; } @@ -72,10 +79,13 @@ export class BaseVisType implements VisType public readonly name; public readonly title; public readonly description; + public readonly note; public readonly getSupportedTriggers; public readonly icon; public readonly image; public readonly stage; + public readonly group; + public readonly titleInWizard; public readonly options; public readonly visualization; public readonly visConfig; @@ -98,6 +108,7 @@ export class BaseVisType implements VisType this.name = opts.name; this.description = opts.description ?? ''; + this.note = opts.note ?? ''; this.getSupportedTriggers = opts.getSupportedTriggers; this.title = opts.title; this.icon = opts.icon; @@ -108,6 +119,8 @@ export class BaseVisType implements VisType this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} }); this.options = defaultsDeep({}, opts.options, defaultOptions); this.stage = opts.stage ?? 'production'; + this.group = opts.group ?? VisGroups.AGGBASED; + this.titleInWizard = opts.titleInWizard ?? ''; this.hidden = opts.hidden ?? false; this.requestHandler = opts.requestHandler ?? 'courier'; this.responseHandler = opts.responseHandler ?? 'none'; diff --git a/src/plugins/visualizations/public/vis_types/index.ts b/src/plugins/visualizations/public/vis_types/index.ts index a46b257c9905c1..a02ac82c8d1223 100644 --- a/src/plugins/visualizations/public/vis_types/index.ts +++ b/src/plugins/visualizations/public/vis_types/index.ts @@ -18,6 +18,7 @@ */ export * from './types_service'; +export { VisGroups } from './types'; export type { VisType } from './types'; export type { BaseVisTypeOptions } from './base_vis_type'; export type { ReactVisTypeOptions } from './react_vis_type'; diff --git a/src/plugins/visualizations/public/vis_types/types.ts b/src/plugins/visualizations/public/vis_types/types.ts index 7206e9612f1024..ee804e5677243c 100644 --- a/src/plugins/visualizations/public/vis_types/types.ts +++ b/src/plugins/visualizations/public/vis_types/types.ts @@ -33,21 +33,63 @@ export interface VisTypeOptions { hierarchicalData: boolean; } +export enum VisGroups { + PROMOTED = 'promoted', + TOOLS = 'tools', + AGGBASED = 'aggbased', +} + /** * A visualization type representing one specific type of "classical" * visualizations (i.e. not Lens visualizations). */ export interface VisType { + /** + * Visualization unique name + */ readonly name: string; + /** + * It is the displayed text on the wizard and the vis listing + */ readonly title: string; + /** + * If given, it will be diplayed on the wizard vis card as the main description. + */ readonly description?: string; + /** + * If given, it will be diplayed on the wizard vis card as a note in italic. + */ + readonly note: string; + /** + * If given, it will return the supported triggers for this vis. + */ readonly getSupportedTriggers?: () => Array; readonly isAccessible?: boolean; readonly requestHandler?: string | unknown; readonly responseHandler?: string | unknown; + /** + * It is the visualization icon, displayed on the wizard. + */ readonly icon?: IconType; + /** + * Except from an icon, an image can be passed + */ readonly image?: string; + /** + * Describes the visualization stage + */ readonly stage: 'experimental' | 'beta' | 'production'; + /** + * Describes the experience group that the visualization belongs. + * It can be on tools, aggregation based or promoted group. + */ + readonly group: VisGroups; + /** + * If given, it will be displayed on the wizard instead of the title. + * We use it because we want to differentiate the vis title from the + * way it is presented on the wizard + */ + readonly titleInWizard: string; readonly requiresSearch: boolean; readonly useCustomNoDataScreen: boolean; readonly hierarchicalData?: boolean | ((vis: { params: TVisParams }) => boolean); diff --git a/src/plugins/visualizations/public/vis_types/types_service.ts b/src/plugins/visualizations/public/vis_types/types_service.ts index 5d619064c240e9..27276eeb889238 100644 --- a/src/plugins/visualizations/public/vis_types/types_service.ts +++ b/src/plugins/visualizations/public/vis_types/types_service.ts @@ -20,7 +20,7 @@ import { visTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry'; import { BaseVisType, BaseVisTypeOptions } from './base_vis_type'; import { ReactVisType, ReactVisTypeOptions } from './react_vis_type'; -import { VisType } from './types'; +import { VisType, VisGroups } from './types'; /** * Vis Types Service @@ -101,6 +101,21 @@ export class TypesService { * returns all registered aliases */ getAliases: visTypeAliasRegistry.get, + /** + * unregisters a visualization alias by its name + * alias is a visualization type without implementation, it just redirects somewhere in kibana + * @param {string} visTypeAliasName - visualization alias name + */ + unRegisterAlias: visTypeAliasRegistry.remove, + /** + * returns all visualizations of specific group + * @param {VisGroups} group - group type (aggbased | other | tools) + */ + getByGroup: (group: VisGroups) => { + return Object.values(this.types).filter((type) => { + return type.group === group; + }); + }, }; } diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 9733e9fd685499..fc5dfd4e123fb7 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -43,9 +43,9 @@ export interface VisualizationsAppExtension { }) => VisualizationListItem; } -export interface VisTypeAliasPromotion { +export interface VisTypeAliasPromoTooltip { description: string; - buttonText: string; + link: string; } export interface VisTypeAlias { @@ -54,8 +54,11 @@ export interface VisTypeAlias { name: string; title: string; icon: string; - promotion?: VisTypeAliasPromotion; + promotion?: boolean; + promoTooltip?: VisTypeAliasPromoTooltip; description: string; + note?: string; + disabled?: boolean; getSupportedTriggers?: () => Array; stage: 'experimental' | 'beta' | 'production'; @@ -65,11 +68,13 @@ export interface VisTypeAlias { }; } -const registry: VisTypeAlias[] = []; +let registry: VisTypeAlias[] = []; +const discardOnRegister: string[] = []; interface VisTypeAliasRegistry { get: () => VisTypeAlias[]; add: (newVisTypeAlias: VisTypeAlias) => void; + remove: (visTypeAliasName: string) => void; } export const visTypeAliasRegistry: VisTypeAliasRegistry = { @@ -78,6 +83,22 @@ export const visTypeAliasRegistry: VisTypeAliasRegistry = { if (registry.find((visTypeAlias) => visTypeAlias.name === newVisTypeAlias.name)) { throw new Error(`${newVisTypeAlias.name} already registered`); } - registry.push(newVisTypeAlias); + // if it exists on discardOnRegister array then we don't allow it to be registered + const isToBeDiscarded = discardOnRegister.some( + (aliasName) => aliasName === newVisTypeAlias.name + ); + if (!isToBeDiscarded) { + registry.push(newVisTypeAlias); + } + }, + remove: (visTypeAliasName) => { + const isAliasPresent = registry.find((visTypeAlias) => visTypeAlias.name === visTypeAliasName); + // in case the alias has not registered yet we store it on an array, in order to not allow it to + // be registered in case of a race condition + if (!isAliasPresent) { + discardOnRegister.push(visTypeAliasName); + } else { + registry = registry.filter((visTypeAlias) => visTypeAlias.name !== visTypeAliasName); + } }, }; diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap deleted file mode 100644 index 2089289b372a2a..00000000000000 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ /dev/null @@ -1,2141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NewVisModal filter for visualization types should render as expected 1`] = ` - - - -
-
- -
-
-
- New Visualization -
-
-
-
-
-
-
-
-
- -
- - -
-
- -
-
-
-
-
- - 3 types found - -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-

- Select a visualization type -

-
-
-

- Start creating your visualization by selecting a type for that visualization. -

-

- - promotion description - -

- -
-
-
-
-
-
-
-
- } - > - - -
-
- - - - - -
- - -
- -
- - New Visualization - -
-
-
-
-
- -
- -
- -
- -
- - -
-
- - - - -
- - - - - -
-
- - - - - -
-
-
-
-
-
-
-
- -
- - - - 3 types found - - - - -
    -
  • - - Vis alias with promotion - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with alias Url - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with search - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis Type 1 - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
-
-
-
-
-
-
-
- -
- -

- - Select a visualization type - -

-
- -
- - - -
-

- - Start creating your visualization by selecting a type for that visualization. - -

-

- - promotion description - -

- - - - - -
-
-
-
- -
- -
- -
-
-
- - - - - -`; - -exports[`NewVisModal should render as expected 1`] = ` - - - -
-
- -
-
-
- New Visualization -
-
-
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-

- Select a visualization type -

-
-
-

- Start creating your visualization by selecting a type for that visualization. -

-

- - promotion description - -

- -
-
-
-
-
-
-
-
- } - > - - -
-
- - - - - -
- - -
- -
- - New Visualization - -
-
-
-
-
- -
- -
- -
- -
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
-
-
- -
- - - - -
    -
  • - - Vis alias with promotion - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis Type 1 - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with alias Url - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with search - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
-
-
-
-
-
-
-
- -
- -

- - Select a visualization type - -

-
- -
- - - -
-

- - Start creating your visualization by selecting a type for that visualization. - -

-

- - promotion description - -

- - - - - -
-
-
-
- -
- -
- -
-
-
- - - - - -`; diff --git a/src/plugins/visualizations/public/wizard/_index.scss b/src/plugins/visualizations/public/wizard/_index.scss deleted file mode 100644 index a10b4b1b347b7e..00000000000000 --- a/src/plugins/visualizations/public/wizard/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'dialog'; diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx new file mode 100644 index 00000000000000..3cbe6a0b604c65 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx @@ -0,0 +1,122 @@ +/* + * 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 { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypesStart, VisType, VisGroups } from '../../vis_types'; +import { AggBasedSelection } from './agg_based_selection'; + +describe('AggBasedSelection', () => { + const defaultVisTypeParams = { + hidden: false, + visualization: class Controller { + public render = jest.fn(); + public destroy = jest.fn(); + }, + requiresSearch: false, + requestHandler: 'none', + responseHandler: 'none', + }; + const _visTypes = [ + { + name: 'vis1', + title: 'Vis Type 1', + stage: 'production', + group: VisGroups.PROMOTED, + ...defaultVisTypeParams, + }, + { + name: 'vis2', + title: 'Vis Type 2', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }, + { + name: 'vis3', + title: 'Vis Type 3', + stage: 'production', + group: VisGroups.AGGBASED, + }, + { + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }, + ] as VisType[]; + + const visTypes: TypesStart = { + get(id: string): VisType { + return _visTypes.find((vis) => vis.name === id) as VisType; + }, + all: () => { + return (_visTypes as unknown) as VisType[]; + }, + getAliases: () => [], + unRegisterAlias: () => [], + getByGroup: (group: VisGroups) => { + return _visTypes.filter((type) => { + return type.group === group; + }) as VisType[]; + }, + }; + + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call the toggleGroups if the user clicks the goBack link', () => { + const toggleGroups = jest.fn(); + const wrapper = mountWithIntl( + + ); + const aggBasedGroupCard = wrapper.find('[data-test-subj="goBackLink"]').at(0); + aggBasedGroupCard.simulate('click'); + expect(toggleGroups).toHaveBeenCalled(); + }); + + describe('filter for visualization types', () => { + it('should render as expected', () => { + const wrapper = mountWithIntl( + + ); + const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); + searchBox.simulate('change', { target: { value: 'with' } }); + expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true); + }); + }); +}); diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx new file mode 100644 index 00000000000000..5d11923ab68307 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx @@ -0,0 +1,163 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import { orderBy } from 'lodash'; +import React, { ChangeEvent } from 'react'; + +import { + EuiFieldSearch, + EuiFlexGrid, + EuiFlexItem, + EuiScreenReaderOnly, + EuiSpacer, + EuiIcon, + EuiCard, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; + +import { memoizeLast } from '../../legacy/memoize'; +import type { VisType, TypesStart } from '../../vis_types'; +import { VisGroups } from '../../vis_types'; +import { DialogNavigation } from '../dialog_navigation'; + +interface VisTypeListEntry { + type: VisType; + highlighted: boolean; +} + +interface AggBasedSelectionProps { + onVisTypeSelected: (visType: VisType) => void; + visTypesRegistry: TypesStart; + toggleGroups: (flag: boolean) => void; +} +interface AggBasedSelectionState { + query: string; +} + +class AggBasedSelection extends React.Component { + public state: AggBasedSelectionState = { + query: '', + }; + + private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes); + + public render() { + const { query } = this.state; + const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query); + return ( + <> + + + + + + + this.props.toggleGroups(true)} /> + + + + + {query && ( + type.highlighted).length, + }} + /> + )} + + + + {visTypes.map(this.renderVisType)} + + + + ); + } + + private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] { + const types = visTypes.getByGroup(VisGroups.AGGBASED).filter((type) => { + // Filter out hidden visualizations and visualizations that are only aggregations based + return !type.hidden; + }); + + let entries: VisTypeListEntry[]; + if (!query) { + entries = types.map((type) => ({ type, highlighted: false })); + } else { + const q = query.toLowerCase(); + entries = types.map((type) => { + const matchesQuery = + type.name.toLowerCase().includes(q) || + type.title.toLowerCase().includes(q) || + (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); + return { type, highlighted: matchesQuery }; + }); + } + + return orderBy(entries, ['highlighted', 'type.title'], ['desc', 'asc']); + } + + private renderVisType = (visType: VisTypeListEntry) => { + const isDisabled = this.state.query !== '' && !visType.highlighted; + const onClick = () => this.props.onVisTypeSelected(visType.type); + + return ( + + {visType.type.title}} + onClick={onClick} + data-test-subj={`visType-${visType.type.name}`} + data-vis-stage={visType.type.stage} + aria-label={`visType-${visType.type.name}`} + description={visType.type.description || ''} + layout="horizontal" + isDisabled={isDisabled} + icon={} + /> + + ); + }; + + private onQueryChange = (ev: ChangeEvent) => { + this.setState({ + query: ev.target.value, + }); + }; +} + +export { AggBasedSelection }; diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts b/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts new file mode 100644 index 00000000000000..c1dfab71cce499 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { AggBasedSelection } from './agg_based_selection'; diff --git a/src/plugins/visualizations/public/wizard/_dialog.scss b/src/plugins/visualizations/public/wizard/dialog.scss similarity index 81% rename from src/plugins/visualizations/public/wizard/_dialog.scss rename to src/plugins/visualizations/public/wizard/dialog.scss index 793951f9dd1ca3..f0cc8381a67aab 100644 --- a/src/plugins/visualizations/public/wizard/_dialog.scss +++ b/src/plugins/visualizations/public/wizard/dialog.scss @@ -1,84 +1,57 @@ -.visNewVisDialog { - max-width: 100vw; - background-image: url(lightOrDarkTheme("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%23F5F7FA' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%23E6EBF2' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E","data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%2318191E' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%2315161B' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E")); - background-repeat: no-repeat; - background-position: calc(100% + 1px) calc(100% + 1px); - background-size: 30%; -} +$modalWidth: $euiSizeL * 34; +$modalHeight: $euiSizeL * 30; -.visNewVisSearchDialog { - width: $euiSizeL * 30; - min-height: $euiSizeL * 25; -} - -.visNewVisDialog__body { - display: flex; - padding: $euiSizeM $euiSizeL 0; - min-height: 0; -} -.visNewVisDialog__list { - min-height: 0; -} +.visNewVisDialog { + @include euiBreakpoint('xs', 's') { + max-height: 100%; + } -.visNewVisDialog__searchWrapper { - flex-shrink: 0; + max-width: $modalWidth; + max-height: $modalHeight; + background: $euiColorEmptyShade; } -.visNewVisDialog__typesWrapper { - @include euiOverflowShadow; - max-width: $euiSizeXXL * 10; - min-height: 0; - margin-top: 2px; // Account for search field dropshadow - overflow: hidden; -} +.visNewVisDialog--aggbased { + @include euiBreakpoint('xs', 's') { + max-height: 100%; + } -.visNewVisDialog__types { - @include euiScrollBar; - // EUITODO: allow for more (calculated) widths of `EuiKeyPadMenu` - width: auto; - overflow-y: auto; - padding-top: $euiSize; - justify-content: center; - padding-bottom: $euiSize; + max-width: $modalWidth; + max-height: $modalHeight; + background-repeat: no-repeat; + background-position: calc(100% + 1px) calc(100% + 1px); + background-size: 30%; + // sass-lint:disable-block quotes space-after-comma + background-image: url(lightOrDarkTheme("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%23F5F7FA' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%23E6EBF2' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E","data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%2318191E' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%2315161B' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E")); } -.visNewVisDialog__description { - width: $euiSizeXL * 10; +.visNewVisSearchDialog { + min-width: $modalWidth; + min-height: $modalHeight; } -.visNewVisDialog__type:disabled { - opacity: 0.2; - pointer-events: none; +.visNewVisDialog__toolsCard { + background-color: transparent; } -.visNewVisDialog__typeImage { - @include size($euiSizeL); +.visNewVisDialog__groupsCard { + background-color: transparent; + box-shadow: none; } -@include euiBreakpoint('xs', 's') { - .visNewVisDialog { - background-image: none; - } - - .visNewVisDialog__typesWrapper { - max-width: none; - } - - .visNewVisDialog__types { - justify-content: flex-start; - } +.visNewVisDialog__groupsCardWrapper { + position: relative; - .visNewVisDialog__description { - display: none; + .visNewVisDialog__groupsCardLink { + position: absolute; + left: 50%; + transform: translateX(-50%); + margin-top: - $euiSizeM; } -} -@include internetExplorerOnly { - .visNewVisDialog { - width: 820px; + // overrides EuiBetaBadge specificity + .visNewVisDialog__groupsCardBetaBadge { + background: $euiColorEmptyShade; + cursor: pointer; } - - .visNewVisDialog__body { - flex-basis: 800px; - } -} +} \ No newline at end of file diff --git a/src/plugins/visualizations/public/wizard/dialog_navigation.tsx b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx new file mode 100644 index 00000000000000..aa8e4c5580f523 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx @@ -0,0 +1,48 @@ +/* + * 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 { EuiLink, EuiIcon, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface DialogNavigationProps { + goBack: () => void; +} + +function DialogNavigation(props: DialogNavigationProps) { + return ( + <> + + + + + + + {i18n.translate('visualizations.newVisWizard.goBackLink', { + defaultMessage: 'Go back', + })} + + + + + + ); +} + +export { DialogNavigation }; diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss b/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss new file mode 100644 index 00000000000000..ce46b872a97ee5 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss @@ -0,0 +1,30 @@ +.visNewVisDialogGroupSelection__body { + // override EUI specificity + .euiModalBody__overflow { + padding: 0 !important; // sass-lint:disable-line no-important + } +} + +.visNewVisDialogGroupSelection__visGroups { + padding: $euiSizeS $euiSizeXL 0; +} + +.visNewVisDialogGroupSelection__footer { + @include euiBreakpoint('xs', 's') { + background: $euiColorEmptyShade; + } + + padding: 0 $euiSizeXL $euiSizeL; + background: $euiColorLightestShade; +} + +.visNewVisDialogGroupSelection__footerDescriptionList { + @include euiBreakpoint('xs', 's') { + padding-top: $euiSizeL; + } +} + +.visNewVisDialogGroupSelection__footerDescriptionListTitle { + // override EUI specificity + width: auto !important; // sass-lint:disable-line no-important +} diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx new file mode 100644 index 00000000000000..b357f42bbae8bc --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx @@ -0,0 +1,321 @@ +/* + * 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 { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypesStart, VisType, VisGroups } from '../../vis_types'; +import { GroupSelection } from './group_selection'; +import { DocLinksStart } from '../../../../../core/public'; + +describe('GroupSelection', () => { + const defaultVisTypeParams = { + hidden: false, + visualization: class Controller { + public render = jest.fn(); + public destroy = jest.fn(); + }, + requiresSearch: false, + requestHandler: 'none', + responseHandler: 'none', + }; + const _visTypes = [ + { + name: 'vis1', + title: 'Vis Type 1', + description: 'Vis Type 1', + stage: 'production', + group: VisGroups.PROMOTED, + ...defaultVisTypeParams, + }, + { + name: 'vis2', + title: 'Vis Type 2', + description: 'Vis Type 2', + group: VisGroups.PROMOTED, + stage: 'production', + ...defaultVisTypeParams, + }, + { + name: 'visWithAliasUrl', + title: 'Vis with alias Url', + aliasApp: 'aliasApp', + aliasPath: '#/aliasApp', + disabled: true, + promoTooltip: { + description: 'Learn More', + link: '#/anotherUrl', + }, + description: 'Vis with alias Url', + stage: 'production', + group: VisGroups.PROMOTED, + }, + { + name: 'visAliasWithPromotion', + title: 'Vis alias with promotion', + description: 'Vis alias with promotion', + stage: 'production', + group: VisGroups.PROMOTED, + aliasApp: 'anotherApp', + aliasPath: '#/anotherUrl', + promotion: true, + } as unknown, + ] as VisType[]; + + const visTypesRegistry = (visTypes: VisType[]): TypesStart => { + return { + get(id: string): VisType { + return (visTypes.find((vis) => vis.name === id) as unknown) as VisType; + }, + all: () => { + return (visTypes as unknown) as VisType[]; + }, + getAliases: () => [], + unRegisterAlias: () => [], + getByGroup: (group: VisGroups) => { + return (visTypes.filter((type) => { + return type.group === group; + }) as unknown) as VisType[]; + }, + }; + }; + + const docLinks = { + links: { + dashboard: { + guide: 'test', + }, + }, + }; + + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the header title', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="groupModalHeader"]').at(0).text()).toBe( + 'New visualization' + ); + }); + + it('should not render the aggBased group card if no aggBased visualization is registered', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(false); + }); + + it('should render the aggBased group card if an aggBased group vis is registered', () => { + const aggBasedVisType = { + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true); + }); + + it('should not render the tools group card if no tools visualization is registered', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(false); + }); + + it('should render the tools group card if a tools group vis is registered', () => { + const toolsVisType = { + name: 'vis3', + title: 'Vis3', + stage: 'production', + group: VisGroups.TOOLS, + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true); + }); + + it('should call the toggleGroups if the aggBased group card is clicked', () => { + const toggleGroups = jest.fn(); + const aggBasedVisType = { + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + const aggBasedGroupCard = wrapper.find('[data-test-subj="visGroupAggBasedExploreLink"]').at(0); + aggBasedGroupCard.simulate('click'); + expect(toggleGroups).toHaveBeenCalled(); + }); + + it('should sort promoted visualizations first', () => { + const wrapper = mountWithIntl( + + ); + + const cards = [ + ...new Set( + wrapper.find('[data-test-subj^="visType-"]').map((card) => card.prop('data-test-subj')) + ), + ]; + + expect(cards).toEqual([ + 'visType-visAliasWithPromotion', + 'visType-vis1', + 'visType-vis2', + 'visType-visWithAliasUrl', + ]); + }); + + it('should render disabled aliases with a disabled class', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').exists()).toBe(true); + expect( + wrapper + .find('[data-test-subj="visType-visWithAliasUrl"]') + .at(1) + .hasClass('euiCard-isDisabled') + ).toBe(true); + }); + + it('should render a basic badge with link for disabled aliases with promoTooltip', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visTypeBadge"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="visTypeBadge"]').at(0).prop('tooltipContent')).toBe( + 'Learn More' + ); + }); + + it('should not show tools experimental visualizations if showExperimentalis false', () => { + const expVis = { + name: 'visExp', + title: 'Experimental Vis', + group: VisGroups.TOOLS, + stage: 'experimental', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); + }); + + it('should show tools experimental visualizations if showExperimental is true', () => { + const expVis = { + name: 'visExp', + title: 'Experimental Vis', + group: VisGroups.TOOLS, + stage: 'experimental', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); + }); +}); diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx new file mode 100644 index 00000000000000..8520b84cc42adb --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx @@ -0,0 +1,294 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import React, { useCallback, useMemo } from 'react'; +import { orderBy } from 'lodash'; +import { + EuiFlexGroup, + EuiFlexGrid, + EuiFlexItem, + EuiCard, + EuiIcon, + EuiModalHeader, + EuiModalBody, + EuiModalHeaderTitle, + EuiLink, + EuiText, + EuiSpacer, + EuiBetaBadge, + EuiTitle, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiDescriptionList, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { DocLinksStart } from '../../../../../core/public'; +import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry'; +import type { VisType, TypesStart } from '../../vis_types'; +import { VisGroups } from '../../vis_types'; +import './group_selection.scss'; + +interface GroupSelectionProps { + onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; + visTypesRegistry: TypesStart; + docLinks: DocLinksStart; + toggleGroups: (flag: boolean) => void; + showExperimental: boolean; +} + +interface VisCardProps { + onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; + visType: VisType | VisTypeAlias; + showExperimental?: boolean | undefined; +} + +function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias { + return 'aliasPath' in type; +} + +function GroupSelection(props: GroupSelectionProps) { + const visualizeGuideLink = props.docLinks.links.dashboard.guide; + const promotedVisGroups = useMemo( + () => + orderBy( + [ + ...props.visTypesRegistry.getAliases(), + ...props.visTypesRegistry.getByGroup(VisGroups.PROMOTED), + ], + ['promotion', 'title'], + ['asc', 'asc'] + ), + [props.visTypesRegistry] + ); + return ( + <> + + + + + + +
+ + + {promotedVisGroups.map((visType) => ( + + ))} + + +
+
+ + + {props.visTypesRegistry.getByGroup(VisGroups.AGGBASED).length > 0 && ( + + props.toggleGroups(false)} + title={ + + {i18n.translate('visualizations.newVisWizard.aggBasedGroupTitle', { + defaultMessage: 'Aggregation based', + })} + + } + data-test-subj="visGroup-aggbased" + description={i18n.translate( + 'visualizations.newVisWizard.aggBasedGroupDescription', + { + defaultMessage: + 'Use our classic visualize library to create charts based on aggregations.', + } + )} + icon={} + className="visNewVisDialog__groupsCard" + > + props.toggleGroups(false)} + > + + {i18n.translate('visualizations.newVisWizard.exploreOptionLinkText', { + defaultMessage: 'Explore options', + })}{' '} + + + + + + )} + {props.visTypesRegistry.getByGroup(VisGroups.TOOLS).length > 0 && ( + + + + + {i18n.translate('visualizations.newVisWizard.toolsGroupTitle', { + defaultMessage: 'Tools', + })} + + + +
+ {props.visTypesRegistry.getByGroup(VisGroups.TOOLS).map((visType) => ( + + ))} +
+
+ )} +
+ + + + + + + + + + + +
+
+ + ); +} + +const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => { + const onClick = useCallback(() => { + onVisTypeSelected(visType); + }, [onVisTypeSelected, visType]); + const shouldDisableCard = isVisTypeAlias(visType) && visType.disabled; + const betaBadgeContent = + shouldDisableCard && 'promoTooltip' in visType ? ( + + + + ) : undefined; + return ( + + {betaBadgeContent} + + {'titleInWizard' in visType && visType.titleInWizard + ? visType.titleInWizard + : visType.title} + + } + onClick={onClick} + isDisabled={shouldDisableCard} + data-test-subj={`visType-${visType.name}`} + data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'} + aria-label={`visType-${visType.name}`} + description={ + <> + {visType.description || ''} + {visType.note || ''} + + } + layout="horizontal" + icon={} + className="visNewVisDialog__groupsCard" + /> + + ); +}; + +const ToolsGroup = ({ visType, onVisTypeSelected, showExperimental }: VisCardProps) => { + const onClick = useCallback(() => { + onVisTypeSelected(visType); + }, [onVisTypeSelected, visType]); + // hide the experimental visualization if lab mode is not enabled + if (!showExperimental && visType.stage === 'experimental') { + return null; + } + return ( + + + + + + + + + {'titleInWizard' in visType && visType.titleInWizard + ? visType.titleInWizard + : visType.title} + + + {visType.stage === 'experimental' && ( + + + + )} + + + {visType.description} + + + + ); +}; + +export { GroupSelection }; diff --git a/src/plugins/visualizations/public/wizard/group_selection/index.ts b/src/plugins/visualizations/public/wizard/group_selection/index.ts new file mode 100644 index 00000000000000..80bd3dda7ac40f --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { GroupSelection } from './group_selection'; diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index 51bcfed2016874..eea364b754e31e 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -19,9 +19,9 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { TypesStart, VisType } from '../vis_types'; -import { NewVisModal } from './new_vis_modal'; -import { ApplicationStart, SavedObjectsStart } from '../../../../core/public'; +import { TypesStart, VisType, VisGroups } from '../vis_types'; +import NewVisModal from './new_vis_modal'; +import { ApplicationStart, SavedObjectsStart, DocLinksStart } from '../../../../core/public'; import { embeddablePluginMock } from '../../../embeddable/public/mocks'; describe('NewVisModal', () => { @@ -36,31 +36,41 @@ describe('NewVisModal', () => { responseHandler: 'none', }; const _visTypes = [ - { name: 'vis', title: 'Vis Type 1', stage: 'production', ...defaultVisTypeParams }, - { name: 'visExp', title: 'Experimental Vis', stage: 'experimental', ...defaultVisTypeParams }, { - name: 'visWithSearch', - title: 'Vis with search', + name: 'vis', + title: 'Vis Type 1', + stage: 'production', + group: VisGroups.PROMOTED, + ...defaultVisTypeParams, + }, + { + name: 'vis2', + title: 'Vis Type 2', + group: VisGroups.PROMOTED, stage: 'production', ...defaultVisTypeParams, }, + { + name: 'vis3', + title: 'Vis3', + stage: 'production', + group: VisGroups.TOOLS, + ...defaultVisTypeParams, + }, { name: 'visWithAliasUrl', title: 'Vis with alias Url', stage: 'production', + group: VisGroups.PROMOTED, aliasApp: 'otherApp', aliasPath: '#/aliasUrl', }, { - name: 'visAliasWithPromotion', - title: 'Vis alias with promotion', + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, stage: 'production', - aliasApp: 'anotherApp', - aliasPath: '#/anotherUrl', - promotion: { - description: 'promotion description', - buttonText: 'another app', - }, + ...defaultVisTypeParams, }, ]; const visTypes: TypesStart = { @@ -71,10 +81,23 @@ describe('NewVisModal', () => { return (_visTypes as unknown) as VisType[]; }, getAliases: () => [], + unRegisterAlias: () => [], + getByGroup: (group: VisGroups) => { + return (_visTypes.filter((type) => { + return type.group === group; + }) as unknown) as VisType[]; + }, }; const addBasePath = (url: string) => `testbasepath${url}`; const settingsGet = jest.fn(); const uiSettings: any = { get: settingsGet }; + const docLinks = { + links: { + dashboard: { + guide: 'test', + }, + }, + }; beforeAll(() => { Object.defineProperty(window, 'location', { @@ -88,7 +111,7 @@ describe('NewVisModal', () => { jest.clearAllMocks(); }); - it('should render as expected', () => { + it('should show the aggbased group but not the visualization assigned to this group', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(false); }); - it('should show a button for regular visualizations', () => { + it('should show the tools group', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true); }); - it('should sort promoted visualizations first', () => { + it('should display the visualizations of the other group', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - expect( - wrapper - .find('button[data-test-subj^="visType-"]') - .map((button) => button.prop('data-test-subj')) - ).toEqual([ - 'visType-visAliasWithPromotion', - 'visType-vis', - 'visType-visWithAliasUrl', - 'visType-visWithSearch', - ]); + expect(wrapper.find('[data-test-subj="visType-vis2"]').exists()).toBe(true); }); describe('open editor', () => { @@ -152,11 +170,12 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-vis"]').at(0); + visCard.simulate('click'); expect(window.location.assign).toBeCalledWith('testbasepath/app/visualize#/create?type=vis'); }); @@ -170,11 +189,12 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-vis"]').at(0); + visCard.simulate('click'); expect(window.location.assign).toBeCalledWith( 'testbasepath/app/visualize#/create?type=vis&foo=true&bar=42' ); @@ -194,12 +214,13 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={({ navigateToApp } as unknown) as ApplicationStart} + docLinks={docLinks as DocLinksStart} stateTransfer={stateTransfer} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').at(0); + visCard.simulate('click'); expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', { path: '#/aliasUrl', state: { originatingApp: 'coolJestTestApp' }, @@ -219,17 +240,18 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={({ navigateToApp } as unknown) as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').at(0); + visCard.simulate('click'); expect(navigateToApp).toBeCalledWith('otherApp', { path: '#/aliasUrl' }); expect(onClose).toHaveBeenCalled(); }); }); - describe('filter for visualization types', () => { + describe('aggBased visualizations', () => { it('should render as expected', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); - searchBox.simulate('change', { target: { value: 'with' } }); - expect(wrapper).toMatchSnapshot(); - }); - }); - - describe('experimental visualizations', () => { - it('should not show experimental visualizations if visualize:enableLabs is false', () => { - settingsGet.mockReturnValue(false); - const wrapper = mountWithIntl( - null} - visTypesRegistry={visTypes} - addBasePath={addBasePath} - uiSettings={uiSettings} - application={{} as ApplicationStart} - savedObjects={{} as SavedObjectsStart} - /> - ); - expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); - }); - - it('should show experimental visualizations if visualize:enableLabs is true', () => { - settingsGet.mockReturnValue(true); - const wrapper = mountWithIntl( - null} - visTypesRegistry={visTypes} - addBasePath={addBasePath} - uiSettings={uiSettings} - application={{} as ApplicationStart} - savedObjects={{} as SavedObjectsStart} - /> - ); - expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); + const aggBasedGroupCard = wrapper + .find('[data-test-subj="visGroupAggBasedExploreLink"]') + .at(0); + aggBasedGroupCard.simulate('click'); + expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true); }); }); }); diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 4bedd3eb1c22ae..fbd4e6ef80d5a2 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -23,13 +23,20 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; -import { ApplicationStart, IUiSettingsClient, SavedObjectsStart } from '../../../../core/public'; +import { + ApplicationStart, + IUiSettingsClient, + SavedObjectsStart, + DocLinksStart, +} from '../../../../core/public'; import { SearchSelection } from './search_selection'; -import { TypeSelection } from './type_selection'; -import { TypesStart, VisType, VisTypeAlias } from '../vis_types'; +import { GroupSelection } from './group_selection'; +import { AggBasedSelection } from './agg_based_selection'; +import type { TypesStart, VisType, VisTypeAlias } from '../vis_types'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; import { EmbeddableStateTransfer } from '../../../embeddable/public'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; +import './dialog.scss'; interface TypeSelectionProps { isOpen: boolean; @@ -38,6 +45,7 @@ interface TypeSelectionProps { editorParams?: string[]; addBasePath: (path: string) => string; uiSettings: IUiSettingsClient; + docLinks: DocLinksStart; savedObjects: SavedObjectsStart; usageCollection?: UsageCollectionSetup; application: ApplicationStart; @@ -48,6 +56,7 @@ interface TypeSelectionProps { interface TypeSelectionState { showSearchVisModal: boolean; + showGroups: boolean; visType?: VisType; } @@ -72,6 +81,7 @@ class NewVisModal extends React.Component @@ -101,19 +113,21 @@ class NewVisModal extends React.Component this.setState({ showSearchVisModal: false })} /> ) : ( - this.setState({ showGroups: flag })} /> ); @@ -185,4 +199,6 @@ class NewVisModal extends React.Component void; visType: VisType; uiSettings: IUiSettingsClient; savedObjects: SavedObjectsStart; + goBack: () => void; } export class SearchSelection extends React.Component { @@ -54,6 +56,7 @@ export class SearchSelection extends React.Component { + import('./new_vis_modal')); + export interface ShowNewVisModalParams { editorParams?: string[]; onClose?: () => void; @@ -66,20 +68,29 @@ export function showNewVisModal({ document.body.appendChild(container); const element = ( - + + + + } + > + + ); ReactDOM.render(element, container); diff --git a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx deleted file mode 100644 index a5b6e8039ba6d5..00000000000000 --- a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx +++ /dev/null @@ -1,73 +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 { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { NewVisHelp } from './new_vis_help'; - -describe('NewVisHelp', () => { - it('should render as expected', () => { - expect( - shallowWithIntl( - {}} - /> - ) - ).toMatchInlineSnapshot(` - -

- -

-

- - Look at this fancy new thing!!! - -

- - Do it now! - -
- `); - }); -}); diff --git a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx deleted file mode 100644 index 5b226a889408f0..00000000000000 --- a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx +++ /dev/null @@ -1,57 +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 { FormattedMessage } from '@kbn/i18n/react'; -import React, { Fragment } from 'react'; -import { EuiText, EuiButton } from '@elastic/eui'; -import { VisTypeAlias } from '../../vis_types'; - -interface Props { - promotedTypes: VisTypeAlias[]; - onPromotionClicked: (visType: VisTypeAlias) => void; -} - -export function NewVisHelp(props: Props) { - return ( - -

- -

- {props.promotedTypes.map((t) => ( - -

- {t.promotion!.description} -

- props.onPromotionClicked(t)} - fill - size="s" - iconType="popout" - iconSide="right" - > - {t.promotion!.buttonText} - -
- ))} -
- ); -} diff --git a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx deleted file mode 100644 index 8c086ed132ae48..00000000000000 --- a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx +++ /dev/null @@ -1,289 +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 { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { orderBy } from 'lodash'; -import React, { ChangeEvent } from 'react'; - -import { - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, - EuiKeyPadMenu, - EuiKeyPadMenuItem, - EuiModalHeader, - EuiModalHeaderTitle, - EuiScreenReaderOnly, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; - -import { memoizeLast } from '../../legacy/memoize'; -import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry'; -import { NewVisHelp } from './new_vis_help'; -import { VisHelpText } from './vis_help_text'; -import { VisTypeIcon } from './vis_type_icon'; -import { VisType, TypesStart } from '../../vis_types'; - -interface VisTypeListEntry { - type: VisType | VisTypeAlias; - highlighted: boolean; -} - -interface TypeSelectionProps { - addBasePath: (path: string) => string; - onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; - visTypesRegistry: TypesStart; - showExperimental: boolean; -} - -interface HighlightedType { - name: string; - title: string; - description?: string; - highlightMsg?: string; -} - -interface TypeSelectionState { - highlightedType: HighlightedType | null; - query: string; -} - -function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias { - return 'aliasPath' in type; -} - -class TypeSelection extends React.Component { - public state: TypeSelectionState = { - highlightedType: null, - query: '', - }; - - private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes); - - public render() { - const { query, highlightedType } = this.state; - const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query); - return ( - - - - - - -
- - - - - - - - - - {query && ( - type.highlighted).length, - }} - /> - )} - - - - {visTypes.map(this.renderVisType)} - - - - - - {highlightedType ? ( - - ) : ( - - -

- -

-
- - t.type) - .filter((t): t is VisTypeAlias => isVisTypeAlias(t) && Boolean(t.promotion))} - onPromotionClicked={this.props.onVisTypeSelected} - /> -
- )} -
-
-
-
- ); - } - - private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] { - const types = visTypes.all().filter((type) => { - // Filter out all lab visualizations if lab mode is not enabled - if (!this.props.showExperimental && type.stage === 'experimental') { - return false; - } - - // Filter out hidden visualizations - if (type.hidden) { - return false; - } - - return true; - }); - - const allTypes = [...types, ...visTypes.getAliases()]; - - let entries: VisTypeListEntry[]; - if (!query) { - entries = allTypes.map((type) => ({ type, highlighted: false })); - } else { - const q = query.toLowerCase(); - entries = allTypes.map((type) => { - const matchesQuery = - type.name.toLowerCase().includes(q) || - type.title.toLowerCase().includes(q) || - (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); - return { type, highlighted: matchesQuery }; - }); - } - - return orderBy( - entries, - ['highlighted', 'type.promotion', 'type.title'], - ['desc', 'asc', 'asc'] - ); - } - - private renderVisType = (visType: VisTypeListEntry) => { - let stage = {}; - let highlightMsg; - if (!isVisTypeAlias(visType.type) && visType.type.stage === 'experimental') { - stage = { - betaBadgeLabel: i18n.translate('visualizations.newVisWizard.experimentalTitle', { - defaultMessage: 'Experimental', - }), - betaBadgeTooltipContent: i18n.translate('visualizations.newVisWizard.experimentalTooltip', { - defaultMessage: - 'This visualization might be changed or removed in a future release and is not subject to the support SLA.', - }), - }; - highlightMsg = i18n.translate('visualizations.newVisWizard.experimentalDescription', { - defaultMessage: - 'This visualization is experimental. The design and implementation are less mature than stable visualizations and might be subject to change.', - }); - } else if (isVisTypeAlias(visType.type) && visType.type.stage === 'beta') { - const aliasDescription = i18n.translate('visualizations.newVisWizard.betaDescription', { - defaultMessage: - 'This visualization is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features', - }); - stage = { - betaBadgeLabel: i18n.translate('visualizations.newVisWizard.betaTitle', { - defaultMessage: 'Beta', - }), - betaBadgeTooltipContent: aliasDescription, - }; - highlightMsg = aliasDescription; - } - - const isDisabled = this.state.query !== '' && !visType.highlighted; - const onClick = () => this.props.onVisTypeSelected(visType.type); - - const highlightedType: HighlightedType = { - title: visType.type.title, - name: visType.type.name, - description: visType.type.description, - highlightMsg, - }; - - return ( - {visType.type.title}} - onClick={onClick} - onFocus={() => this.setHighlightType(highlightedType)} - onMouseEnter={() => this.setHighlightType(highlightedType)} - onMouseLeave={() => this.setHighlightType(null)} - onBlur={() => this.setHighlightType(null)} - className="visNewVisDialog__type" - data-test-subj={`visType-${visType.type.name}`} - data-vis-stage={!isVisTypeAlias(visType.type) ? visType.type.stage : 'alias'} - disabled={isDisabled} - aria-describedby={`visTypeDescription-${visType.type.name}`} - {...stage} - > - - - ); - }; - - private setHighlightType(highlightedType: HighlightedType | null) { - this.setState({ - highlightedType, - }); - } - - private onQueryChange = (ev: ChangeEvent) => { - this.setState({ - query: ev.target.value, - }); - }; -} - -export { TypeSelection }; diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js index f5c2496a9a5aaf..c315828c594fa0 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.js +++ b/test/functional/apps/dashboard/create_and_add_embeddables.js @@ -42,10 +42,11 @@ export default function ({ getService, getPageObjects }) { }); describe('add new visualization link', () => { - it('adds new visualiztion via the top nav link', async () => { + it('adds new visualization via the top nav link', async () => { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); await PageObjects.dashboard.switchToEditMode(); await dashboardAddPanel.clickCreateNewLink(); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( @@ -63,6 +64,7 @@ export default function ({ getService, getPageObjects }) { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); await dashboardAddPanel.ensureAddPanelIsShowing(); await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( diff --git a/test/functional/apps/dashboard/view_edit.js b/test/functional/apps/dashboard/view_edit.js index 589a46b7e9d08d..a6d81da131751a 100644 --- a/test/functional/apps/dashboard/view_edit.js +++ b/test/functional/apps/dashboard/view_edit.js @@ -134,6 +134,7 @@ export default function ({ getService, getPageObjects }) { await dashboardAddPanel.ensureAddPanelIsShowing(); await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess('new viz panel', { diff --git a/test/functional/apps/getting_started/_shakespeare.js b/test/functional/apps/getting_started/_shakespeare.js index 38ed3edc7deb55..a648dae99158d6 100644 --- a/test/functional/apps/getting_started/_shakespeare.js +++ b/test/functional/apps/getting_started/_shakespeare.js @@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }) { */ it('should create initial vertical bar chart', async function () { log.debug('create shakespeare vertical bar chart'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch('shakespeare'); await PageObjects.visChart.waitForVisualization(); diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index 9ac2160a359da1..b943e070e1768a 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }) { const initAreaChart = async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickAreaChart'); await PageObjects.visualize.clickAreaChart(); log.debug('clickNewSearch'); @@ -390,7 +390,7 @@ export default function ({ getService, getPageObjects }) { const toTime = 'Jan 1, 2020 @ 00:00:00.000'; it('should render a yearly area with 12 svg paths', async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickAreaChart'); await PageObjects.visualize.clickAreaChart(); log.debug('clickNewSearch'); @@ -413,7 +413,7 @@ export default function ({ getService, getPageObjects }) { }); it('should render monthly areas with 168 svg paths', async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickAreaChart'); await PageObjects.visualize.clickAreaChart(); log.debug('clickNewSearch'); diff --git a/test/functional/apps/visualize/_chart_types.ts b/test/functional/apps/visualize/_chart_types.ts index ecb7e9630c2c65..4864fcbf3af095 100644 --- a/test/functional/apps/visualize/_chart_types.ts +++ b/test/functional/apps/visualize/_chart_types.ts @@ -33,10 +33,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualize.navigateToNewVisualization(); }); - it('should show the correct chart types', async function () { + it('should show the promoted vis types for the first step', async function () { + const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB']; + log.debug('oss= ' + isOss); + + // find all the chart types and make sure there all there + const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort(); + log.debug('returned chart types = ' + chartTypes); + log.debug('expected chart types = ' + expectedChartTypes); + expect(chartTypes).to.eql(expectedChartTypes); + }); + + it('should show the correct agg based chart types', async function () { + await PageObjects.visualize.clickAggBasedVisualizations(); let expectedChartTypes = [ 'Area', - 'Controls', 'Coordinate Map', 'Data Table', 'Gauge', @@ -44,18 +55,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Heat Map', 'Horizontal Bar', 'Line', - 'Markdown', 'Metric', 'Pie', 'Region Map', - 'TSVB', 'Tag Cloud', 'Timelion', - 'Vega', 'Vertical Bar', ]; if (!isOss) { - expectedChartTypes.push('Maps', 'Lens'); expectedChartTypes = _.remove(expectedChartTypes, function (n) { return n !== 'Coordinate Map'; }); diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index a1389a69666ae1..bd7511d373b90e 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); log.debug('clickNewSearch'); @@ -107,7 +107,7 @@ export default function ({ getService, getPageObjects }) { } // load a plain table - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -152,7 +152,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data when using average pipeline aggregation', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -167,7 +167,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with date histogram', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -189,7 +189,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with date histogram', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -224,7 +224,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with top hits', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -238,7 +238,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with range agg', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -257,7 +257,7 @@ export default function ({ getService, getPageObjects }) { describe('otherBucket', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -295,7 +295,7 @@ export default function ({ getService, getPageObjects }) { describe('metricsOnAllLevels', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -389,7 +389,7 @@ export default function ({ getService, getPageObjects }) { describe('split tables', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_data_table_nontimeindex.js b/test/functional/apps/visualize/_data_table_nontimeindex.js index fd06257a91ff4f..f45e40970a57fd 100644 --- a/test/functional/apps/visualize/_data_table_nontimeindex.js +++ b/test/functional/apps/visualize/_data_table_nontimeindex.js @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); log.debug('clickNewSearch'); @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data when using average pipeline aggregation', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }) { describe('data table with date histogram', async () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED diff --git a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts index 9fb6d793eb7d9d..cdcf08d2514e5d 100644 --- a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts +++ b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts @@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); log.debug('clickNewSearch'); diff --git a/test/functional/apps/visualize/_embedding_chart.js b/test/functional/apps/visualize/_embedding_chart.js index 5316bf51fd48c2..773bd63d8892f4 100644 --- a/test/functional/apps/visualize/_embedding_chart.js +++ b/test/functional/apps/visualize/_embedding_chart.js @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }) { describe('embedding', () => { describe('a data table', () => { before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_experimental_vis.js b/test/functional/apps/visualize/_experimental_vis.js index e87a36434b2314..c85d66eed12d21 100644 --- a/test/functional/apps/visualize/_experimental_vis.js +++ b/test/functional/apps/visualize/_experimental_vis.js @@ -27,7 +27,7 @@ export default ({ getService, getPageObjects }) => { describe('experimental visualizations', () => { beforeEach(async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.waitForVisualizationSelectPage(); }); diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index 0f870b1fb545fb..06f5913aec8144 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { describe('gauge chart', function indexPatternCreation() { async function initGaugeVis() { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickGauge'); await PageObjects.visualize.clickGauge(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_heatmap_chart.js b/test/functional/apps/visualize/_heatmap_chart.js index 98a0104629c851..e48173c290372a 100644 --- a/test/functional/apps/visualize/_heatmap_chart.js +++ b/test/functional/apps/visualize/_heatmap_chart.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickHeatmapChart'); await PageObjects.visualize.clickHeatmapChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_histogram_request_start.js b/test/functional/apps/visualize/_histogram_request_start.js index f6440cfbd76ffc..8489cffa805da2 100644 --- a/test/functional/apps/visualize/_histogram_request_start.js +++ b/test/functional/apps/visualize/_histogram_request_start.js @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }) { describe('histogram agg onSearchRequestStart', function () { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js index 104184050e838b..426e61d9636ad8 100644 --- a/test/functional/apps/visualize/_inspector.js +++ b/test/functional/apps/visualize/_inspector.js @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }) { describe('inspector', function describeIndexTests() { before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index 8dfc4d352b1331..8c6dbd3461a4f5 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) { const initLineChart = async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickLineChart'); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); @@ -244,7 +244,7 @@ export default function ({ getService, getPageObjects }) { describe('pipeline aggregations', () => { before(async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickLineChart'); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_linked_saved_searches.ts b/test/functional/apps/visualize/_linked_saved_searches.ts index 4151e0e9b471c5..a5a9c47d3d0102 100644 --- a/test/functional/apps/visualize/_linked_saved_searches.ts +++ b/test/functional/apps/visualize/_linked_saved_searches.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should create a visualization from a saved search', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickSavedSearch(savedSearchName); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js index e02dac11d9e982..483a0688d6acad 100644 --- a/test/functional/apps/visualize/_metric_chart.js +++ b/test/functional/apps/visualize/_metric_chart.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { describe('metric chart', function () { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickMetric'); await PageObjects.visualize.clickMetric(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_pie_chart.js b/test/functional/apps/visualize/_pie_chart.js index b68a88c4f97f33..eef0f90005a436 100644 --- a/test/functional/apps/visualize/_pie_chart.js +++ b/test/functional/apps/visualize/_pie_chart.js @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) { const vizName1 = 'Visualization PieChart'; before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }) { it('should show other and missing bucket', async function () { const expectedTableData = ['win 8', 'win xp', 'win 7', 'ios', 'Missing', 'Other']; - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -292,7 +292,7 @@ export default function ({ getService, getPageObjects }) { describe('empty time window', () => { it('should show no data message when no data on selected timerange', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -321,7 +321,7 @@ export default function ({ getService, getPageObjects }) { describe('multi series slice', () => { before(async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -443,7 +443,7 @@ export default function ({ getService, getPageObjects }) { }); it('should still showing pie chart when a subseries have zero data', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -471,7 +471,7 @@ export default function ({ getService, getPageObjects }) { describe('split chart', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index c88670ee8b7414..a671cb82ab70fe 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }) { async function initChart() { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickLineChart'); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); @@ -197,7 +197,7 @@ export default function ({ getService, getPageObjects }) { describe('show values on chart', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -232,7 +232,7 @@ export default function ({ getService, getPageObjects }) { const customLabel = 'myLabel'; const axisTitle = 'myTitle'; before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visEditor.selectYAxisAggregation('Average', 'bytes', customLabel, 1); diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js index f2fe1527c97c28..eee73d2fe76f9b 100644 --- a/test/functional/apps/visualize/_region_map.js +++ b/test/functional/apps/visualize/_region_map.js @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickRegionMap'); await PageObjects.visualize.clickRegionMap(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js index b5c575edb8a0a1..7b1671aaeb2f26 100644 --- a/test/functional/apps/visualize/_tag_cloud.js +++ b/test/functional/apps/visualize/_tag_cloud.js @@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTagCloud'); await PageObjects.visualize.clickTagCloud(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_tile_map.js b/test/functional/apps/visualize/_tile_map.js index 56585ccd725d80..3ae72fe73a5fda 100644 --- a/test/functional/apps/visualize/_tile_map.js +++ b/test/functional/apps/visualize/_tile_map.js @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }) { await browser.setWindowSize(1280, 1000); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTileMap'); await PageObjects.visualize.clickTileMap(); await PageObjects.visualize.clickNewSearch(); @@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }) { await browser.setWindowSize(1280, 1000); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTileMap'); await PageObjects.visualize.clickTileMap(); await PageObjects.visualize.clickNewSearch(); @@ -240,7 +240,7 @@ export default function ({ getService, getPageObjects }) { await browser.setWindowSize(1280, 1000); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTileMap'); await PageObjects.visualize.clickTileMap(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index ff0423eb531da9..5ecbf3f720ee67 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) { const initBarChart = async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickVerticalBarChart'); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }) { }); it('should not filter out first label after rotation of the chart', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }) { describe('bar charts range on x axis', () => { it('should individual bars for each configured range', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js index f95781c9bbb337..7fb123f2e304ab 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js +++ b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { const initBarChart = async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickVerticalBarChart'); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch( diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 9619c81370cd8d..b58acd0bd90592 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -47,6 +47,14 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide await listingTable.clickNewButton('createVisualizationPromptButton'); } + public async clickAggBasedVisualizations() { + await testSubjects.click('visGroupAggBasedExploreLink'); + } + + public async goBackToGroups() { + await testSubjects.click('goBackLink'); + } + public async createVisualizationPromptButton() { await testSubjects.click('createVisualizationPromptButton'); } @@ -59,6 +67,21 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide .map((chart) => $(chart).findTestSubject('visTypeTitle').text().trim()); } + public async getPromotedVisTypes() { + const chartTypeField = await testSubjects.find('visNewDialogGroups'); + const $ = await chartTypeField.parseDomContent(); + const promotedVisTypes: string[] = []; + $('button') + .toArray() + .forEach((chart) => { + const title = $(chart).findTestSubject('visTypeTitle').text().trim(); + if (title) { + promotedVisTypes.push(title); + } + }); + return promotedVisTypes; + } + public async waitForVisualizationSelectPage() { await retry.try(async () => { const visualizeSelectTypePage = await testSubjects.find('visNewDialogTypes'); @@ -68,10 +91,27 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide }); } + public async waitForGroupsSelectPage() { + await retry.try(async () => { + const visualizeSelectGroupStep = await testSubjects.find('visNewDialogGroups'); + if (!(await visualizeSelectGroupStep.isDisplayed())) { + throw new Error('wait for vis groups select step'); + } + }); + } + public async navigateToNewVisualization() { await common.navigateToApp('visualize'); await header.waitUntilLoadingHasFinished(); await this.clickNewVisualization(); + await this.waitForGroupsSelectPage(); + } + + public async navigateToNewAggBasedVisualization() { + await common.navigateToApp('visualize'); + await header.waitUntilLoadingHasFinished(); + await this.clickNewVisualization(); + await this.clickAggBasedVisualizations(); await this.waitForVisualizationSelectPage(); } diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts index a5c16010d3ebaa..b35ef1e8f2f9a9 100644 --- a/test/functional/services/dashboard/visualizations.ts +++ b/test/functional/services/dashboard/visualizations.ts @@ -147,6 +147,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F await PageObjects.dashboard.switchToEditMode(); } await this.ensureNewVisualizationDialogIsShowing(); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickMetric(); await find.clickByCssSelector('li.euiListGroupItem:nth-of-type(2)'); await testSubjects.exists('visualizeSaveButton'); diff --git a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js index 0846780f75ff61..1971754d5304e5 100644 --- a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js +++ b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { describe('self changing vis', function describeIndexTests() { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVisType('self_changing_vis'); }); diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 2da67f81122ab1..ce78757676bccb 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -19,5 +19,5 @@ "optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], - "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"] + "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "lensOss"] } diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 36237eeb6b05f0..2f9310ee24ae9a 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -9,7 +9,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/p import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import { DashboardStart } from 'src/plugins/dashboard/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; -import { VisualizationsSetup } from 'src/plugins/visualizations/public'; +import { VisualizationsSetup, VisualizationsStart } from 'src/plugins/visualizations/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { UrlForwardingSetup } from 'src/plugins/url_forwarding/public'; import { GlobalSearchPluginSetup } from '../../global_search/public'; @@ -35,6 +35,7 @@ import { VISUALIZE_FIELD_TRIGGER, } from '../../../../src/plugins/ui_actions/public'; import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; +import { PLUGIN_ID_OSS } from '../../../../src/plugins/lens_oss/common/constants'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; @@ -58,6 +59,7 @@ export interface LensPluginStartDependencies { navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; dashboard: DashboardStart; + visualizations: VisualizationsStart; embeddable: EmbeddableStart; charts: ChartsPluginStart; savedObjectsTagging?: SavedObjectTaggingPluginStart; @@ -170,6 +172,8 @@ export class LensPlugin { start(core: CoreStart, startDependencies: LensPluginStartDependencies) { this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; + // unregisters the OSS alias + startDependencies.visualizations.unRegisterAlias(PLUGIN_ID_OSS); // unregisters the Visualize action and registers the lens one if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts index d0dceed03db2fe..e20dcfacd5ab5d 100644 --- a/x-pack/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/plugins/lens/public/vis_type_alias.ts @@ -12,19 +12,16 @@ export const getLensAliasConfig = (): VisTypeAlias => ({ aliasPath: getBasePath(), aliasApp: 'lens', name: 'lens', - promotion: { - description: i18n.translate('xpack.lens.visTypeAlias.promotion.description', { - defaultMessage: 'Try Lens, our new, intuitive way to create visualizations.', - }), - buttonText: i18n.translate('xpack.lens.visTypeAlias.promotion.buttonText', { - defaultMessage: 'Go to Lens', - }), - }, + promotion: true, title: i18n.translate('xpack.lens.visTypeAlias.title', { defaultMessage: 'Lens', }), description: i18n.translate('xpack.lens.visTypeAlias.description', { - defaultMessage: `Lens is a simpler way to create basic visualizations`, + defaultMessage: + 'Create visualizations with our drag and drop editor. Switch between visualization types at any time.', + }), + note: i18n.translate('xpack.lens.visTypeAlias.note', { + defaultMessage: 'Recommended for most users.', }), icon: 'lensApp', stage: 'production', diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 6f3a5b61ddc6c5..f52feb3552abb2 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -22,5 +22,5 @@ "ui": true, "server": true, "extraPublicDirs": ["common/constants"], - "requiredBundles": ["kibanaReact", "kibanaUtils", "home"] + "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss"] } diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.js b/x-pack/plugins/maps/public/maps_vis_type_alias.js index b7e95cdf987db0..a2e76216c7def2 100644 --- a/x-pack/plugins/maps/public/maps_vis_type_alias.js +++ b/x-pack/plugins/maps/public/maps_vis_type_alias.js @@ -16,14 +16,6 @@ export function getMapsVisTypeAlias(visualizations, showMapVisualizationTypes) { defaultMessage: 'Create and style maps with multiple layers and indices.', }); - const legacyMapVisualizationWarning = i18n.translate( - 'xpack.maps.visTypeAlias.legacyMapVizWarning', - { - defaultMessage: `Use the Maps app instead of Coordinate Map and Region Map. -The Maps app offers more functionality and is easier to use.`, - } - ); - return { aliasApp: APP_ID, aliasPath: `/${MAP_PATH}`, @@ -31,9 +23,7 @@ The Maps app offers more functionality and is easier to use.`, title: i18n.translate('xpack.maps.visTypeAlias.title', { defaultMessage: 'Maps', }), - description: showMapVisualizationTypes - ? `${description} ${legacyMapVisualizationWarning}` - : description, + description: description, icon: APP_ICON, stage: 'production', }; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 0b797c7b8ef608..75a3f8ef5ede84 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -28,8 +28,12 @@ import { featureCatalogueEntry } from './feature_catalogue_entry'; // @ts-ignore import { getMapsVisTypeAlias } from './maps_vis_type_alias'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; -import { VisualizationsSetup } from '../../../../src/plugins/visualizations/public'; +import { + VisualizationsSetup, + VisualizationsStart, +} from '../../../../src/plugins/visualizations/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; +import { PLUGIN_ID_OSS } from '../../../../src/plugins/maps_oss/common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; import { createMapsUrlGenerator, @@ -72,6 +76,7 @@ export interface MapsPluginStartDependencies { navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; share: SharePluginStart; + visualizations: VisualizationsStart; savedObjects: SavedObjectsStart; } @@ -145,6 +150,8 @@ export class MapsPlugin setLicensingPluginStart(plugins.licensing); plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction); setStartServices(core, plugins); + // unregisters the OSS alias + plugins.visualizations.unRegisterAlias(PLUGIN_ID_OSS); return { createSecurityLayerDescriptors, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b70420e4338af8..595c925f825bdd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2727,7 +2727,6 @@ "inputControl.listControl.disableTooltip": "「{label}」が設定されるまで無効です。", "inputControl.listControl.unableToFetchTooltip": "用語を取得できません、エラー: {errorMessage}", "inputControl.rangeControl.unableToFetchTooltip": "範囲 (最低値と最高値) を取得できません、エラー: {errorMessage}", - "inputControl.register.controlsDescription": "ダッシュボードを簡単に操作できるように、インタラクティブなコントロールを作成します。", "inputControl.register.controlsTitle": "コントロール", "inputControl.register.tabs.controlsTitle": "コントロール", "inputControl.register.tabs.optionsTitle": "オプション", @@ -3972,7 +3971,6 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最終値", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "選択してください", "visTypeTimeseries.indexPatternLabel": "インデックスパターン", - "visTypeTimeseries.kbnVisTypes.metricsDescription": "ビジュアルパイプラインインターフェースを使用して時系列のチャートを作成します。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.markdown.alignOptions.bottomLabel": "一番下", "visTypeTimeseries.markdown.alignOptions.middleLabel": "真ん中", @@ -4327,7 +4325,6 @@ "visTypeVega.mapView.minZoomAndMaxZoomHaveBeenSwappedWarningMessage": "{minZoomPropertyName} と {maxZoomPropertyName} が交換されました", "visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "{name} を {max} にリセットしています", "visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "{name} を {min} にリセットしています", - "visTypeVega.type.vegaDescription": "Vega と Vega-Lite を使用してカスタムビジュアライゼーションを作成します。", "visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} には「{formLink}」の形で {urlParam} パラメーターが必要です", "visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "{urlObject} を使用するには {subObjectName} サブオブジェクトが必要です", "visTypeVega.vegaParser.autoSizeDoesNotAllowFalse": "{autoSizeParam}が有効です。無効にするには、{autoSizeParam}を{noneParam}に設定してください", @@ -4475,7 +4472,6 @@ "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", "visTypeVislib.gauge.alignmentHorizontalTitle": "横", "visTypeVislib.gauge.alignmentVerticalTitle": "縦", - "visTypeVislib.gauge.gaugeDescription": "ゲージはメトリックのステータスを示します。メトリックの値とリファレンスしきい値との関連性を示すのに使用します。", "visTypeVislib.gauge.gaugeTitle": "ゲージ", "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", "visTypeVislib.gauge.gaugeTypes.circleText": "円", @@ -4558,21 +4554,16 @@ "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", "visualizations.functions.visualization.help": "シンプルなビジュアライゼーションです", "visualizations.initializeWithoutIndexPatternErrorMessage": "インデックスパターンなしで集約を初期化しようとしています", - "visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", - "visualizations.newVisWizard.betaTitle": "ベータ", "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", - "visualizations.newVisWizard.experimentalDescription": "このビジュアライゼーションは実験的なものです。デザインと導入は安定したビジュアライゼーションよりも完成度が低く、変更される可能性があります。", "visualizations.newVisWizard.experimentalTitle": "実験的", "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", "visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング", - "visualizations.newVisWizard.helpText": "タイプを選択してビジュアライゼーションの作成を始めましょう。", "visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。", "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", "visualizations.newVisWizard.resultsFound": "{resultCount} 個の{resultCount, plural, one {タイプ} other {タイプ} } が見つかりました", "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。", "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索", - "visualizations.newVisWizard.selectVisType": "ビジュアライゼーションのタイプを選択してください", "visualizations.newVisWizard.title": "新規ビジュアライゼーション", "visualizations.noResultsFoundTitle": "結果が見つかりませんでした", "visualizations.savedObjectName": "ビジュアライゼーション", @@ -10916,9 +10907,6 @@ "xpack.lens.sugegstion.refreshSuggestionLabel": "更新", "xpack.lens.suggestion.refreshSuggestionTooltip": "選択したビジュアライゼーションに基づいて、候補を更新します。", "xpack.lens.suggestions.currentVisLabel": "現在", - "xpack.lens.visTypeAlias.description": "レンズは基本的なビジュアライゼーションを作成するシンプルな方法です", - "xpack.lens.visTypeAlias.promotion.buttonText": "レンズに移動", - "xpack.lens.visTypeAlias.promotion.description": "レンズは直感的に使える新しいビジュアライゼーションの作成方法です。お試しください。", "xpack.lens.visTypeAlias.title": "レンズビジュアライゼーション", "xpack.lens.visTypeAlias.type": "レンズ", "xpack.lens.xyChart.addLayer": "レイヤーを追加", @@ -11675,8 +11663,6 @@ "xpack.maps.viewControl.latLabel": "緯度:", "xpack.maps.viewControl.lonLabel": "経度:", "xpack.maps.viewControl.zoomLabel": "ズーム:", - "xpack.maps.visTypeAlias.description": "マップを作成し、複数のレイヤーとインデックスを使用して、スタイルを設定します。", - "xpack.maps.visTypeAlias.legacyMapVizWarning": "座標マップと地域マップの代わりに、マップアプリを使用します。\nマップアプリはより機能が豊富で、使いやすくなっています。", "xpack.maps.visTypeAlias.title": "マップ", "xpack.maps.xyztmssource.attributionLink": "属性テキストにはリンクが必要です", "xpack.maps.xyztmssource.attributionLinkLabel": "属性リンク", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 14b29468d843cf..bdcb3b8b207814 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2728,7 +2728,6 @@ "inputControl.listControl.disableTooltip": "设置 “{label}” 之前禁用。", "inputControl.listControl.unableToFetchTooltip": "无法提取词,错误:{errorMessage}", "inputControl.rangeControl.unableToFetchTooltip": "无法提取范围最小值和最大值,错误:{errorMessage}", - "inputControl.register.controlsDescription": "创建交互控件,以方便仪表板操控。", "inputControl.register.controlsTitle": "控件", "inputControl.register.tabs.controlsTitle": "控件", "inputControl.register.tabs.optionsTitle": "选项", @@ -3973,7 +3972,6 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最后值", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "选择", "visTypeTimeseries.indexPatternLabel": "索引模式", - "visTypeTimeseries.kbnVisTypes.metricsDescription": "使用可视化管道界面构建时间序列", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.markdown.alignOptions.bottomLabel": "下", "visTypeTimeseries.markdown.alignOptions.middleLabel": "中", @@ -4328,7 +4326,6 @@ "visTypeVega.mapView.minZoomAndMaxZoomHaveBeenSwappedWarningMessage": "已交换 {minZoomPropertyName} 和 {maxZoomPropertyName}", "visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "将 {name} 重置为 {max}", "visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "将 {name} 重置为 {min}", - "visTypeVega.type.vegaDescription": "使用 Vega 和 Vega-Lite 创建定制可视化", "visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} 需要以“{formLink}”形式的 {urlParam} 参数", "visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "使用 {urlObject} 应具有 {subObjectName} 子对象", "visTypeVega.vegaParser.autoSizeDoesNotAllowFalse": "{autoSizeParam} 已启用,只能通过将 {autoSizeParam} 设置为 {noneParam} 来禁用", @@ -4477,7 +4474,6 @@ "visTypeVislib.gauge.alignmentAutomaticTitle": "自动", "visTypeVislib.gauge.alignmentHorizontalTitle": "水平", "visTypeVislib.gauge.alignmentVerticalTitle": "垂直", - "visTypeVislib.gauge.gaugeDescription": "仪表盘图指示指标的状态。用于显示指标值与参考阈值的相关程度。", "visTypeVislib.gauge.gaugeTitle": "仪表盘图", "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", "visTypeVislib.gauge.gaugeTypes.circleText": "圆形", @@ -4560,21 +4556,16 @@ "visualizations.function.visDimension.help": "生成 visConfig 维度对象", "visualizations.functions.visualization.help": "简单可视化", "visualizations.initializeWithoutIndexPatternErrorMessage": "正在尝试在不使用索引模式的情况下初始化聚合", - "visualizations.newVisWizard.betaDescription": "此可视化为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束", - "visualizations.newVisWizard.betaTitle": "公测版", "visualizations.newVisWizard.chooseSourceTitle": "选择源", - "visualizations.newVisWizard.experimentalDescription": "这是实验性可视化。与稳定的可视化相比,其设计和实现均不够成熟,可能会随时发生更改。", "visualizations.newVisWizard.experimentalTitle": "实验性", "visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", "visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型", - "visualizations.newVisWizard.helpText": "通过为该可视化选择类型,来开始创建您的可视化。", "visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,来开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。", "visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}", "visualizations.newVisWizard.resultsFound": "找到了 {resultCount} 个{resultCount, plural, one {类型} other {类型} }", "visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。", "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式", "visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索", - "visualizations.newVisWizard.selectVisType": "选择可视化类型", "visualizations.newVisWizard.title": "新建可视化", "visualizations.noResultsFoundTitle": "找不到结果", "visualizations.savedObjectName": "可视化", @@ -10929,9 +10920,6 @@ "xpack.lens.sugegstion.refreshSuggestionLabel": "刷新", "xpack.lens.suggestion.refreshSuggestionTooltip": "基于选定可视化刷新建议。", "xpack.lens.suggestions.currentVisLabel": "当前", - "xpack.lens.visTypeAlias.description": "Lens 简化了基本可视化的创建", - "xpack.lens.visTypeAlias.promotion.buttonText": "前往 Lens", - "xpack.lens.visTypeAlias.promotion.description": "试用 Lens,以全新直观的方式创建可视化。", "xpack.lens.visTypeAlias.title": "Lens 可视化", "xpack.lens.visTypeAlias.type": "Lens", "xpack.lens.xyChart.addLayer": "添加图层", @@ -11688,8 +11676,6 @@ "xpack.maps.viewControl.latLabel": "纬度:", "xpack.maps.viewControl.lonLabel": "经度:", "xpack.maps.viewControl.zoomLabel": "缩放:", - "xpack.maps.visTypeAlias.description": "使用多个图层和索引创建地图并提供样式。", - "xpack.maps.visTypeAlias.legacyMapVizWarning": "使用 Maps 应用而非坐标地图和区域地图。\nMaps 应用提供更多的功能并更加易用。", "xpack.maps.visTypeAlias.title": "Maps", "xpack.maps.xyztmssource.attributionLink": "属性文本必须附带链接", "xpack.maps.xyztmssource.attributionLinkLabel": "属性链接", diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 3bf697bd97b14f..1a7c0d52cad5ce 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -65,6 +65,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/reporting_api_integration/reporting_without_security.config.ts'), require.resolve('../test/security_solution_endpoint_api_int/config.ts'), require.resolve('../test/ingest_manager_api_integration/config.ts'), + require.resolve('../test/functional_vis_wizard/config.ts'), require.resolve('../test/saved_object_tagging/functional/config.ts'), require.resolve('../test/saved_object_tagging/api_integration/security_and_spaces/config.ts'), require.resolve('../test/saved_object_tagging/api_integration/tagging_api/config.ts'), diff --git a/x-pack/test/functional/apps/lens/persistent_context.ts b/x-pack/test/functional/apps/lens/persistent_context.ts index a115b720f6f2c2..467a33fb018546 100644 --- a/x-pack/test/functional/apps/lens/persistent_context.ts +++ b/x-pack/test/functional/apps/lens/persistent_context.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await filterBar.toggleFilterEnabled('ip'); await appsMenu.clickLink('Visualize', { category: 'kibana' }); await PageObjects.visualize.clickNewVisualization(); - await PageObjects.visualize.waitForVisualizationSelectPage(); + await PageObjects.visualize.waitForGroupsSelectPage(); await PageObjects.visualize.clickVisType('lens'); const timeRange = await PageObjects.timePicker.getTimeConfig(); expect(timeRange.start).to.equal('Sep 7, 2015 @ 06:31:44.000'); diff --git a/x-pack/test/functional/apps/maps/visualize_create_menu.js b/x-pack/test/functional/apps/maps/visualize_create_menu.js index ef39771d6be075..549901884d39b0 100644 --- a/x-pack/test/functional/apps/maps/visualize_create_menu.js +++ b/x-pack/test/functional/apps/maps/visualize_create_menu.js @@ -31,6 +31,7 @@ export default function ({ getService, getPageObjects }) { }); it('should not show legacy region map visualizion in create menu', async () => { + await PageObjects.visualize.clickAggBasedVisualizations(); const hasLegecyViz = await PageObjects.visualize.hasRegionMap(); expect(hasLegecyViz).to.equal(false); }); @@ -41,6 +42,7 @@ export default function ({ getService, getPageObjects }) { }); it('should take users to Maps application when Maps is clicked', async () => { + await PageObjects.visualize.goBackToGroups(); await PageObjects.visualize.clickMapsApp(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.maps.waitForLayersToLoad(); diff --git a/x-pack/test/functional/apps/security/rbac_phase1.js b/x-pack/test/functional/apps/security/rbac_phase1.js index b138859d01361c..58c72eaa3072ec 100644 --- a/x-pack/test/functional/apps/security/rbac_phase1.js +++ b/x-pack/test/functional/apps/security/rbac_phase1.js @@ -105,7 +105,7 @@ export default function ({ getService, getPageObjects }) { log.debug('log in as kibanauser with rbac_all role'); await PageObjects.security.login('kibanauser', 'changeme'); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickVerticalBarChart'); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts index 07c7a1db1e72ae..b3a89b7df3d344 100644 --- a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts +++ b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('works in visualizations', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch('histogram-test'); await PageObjects.visChart.waitForVisualization(); diff --git a/x-pack/test/functional/apps/visualize/reporting.ts b/x-pack/test/functional/apps/visualize/reporting.ts index 0752971ba832ba..71172dbdc14058 100644 --- a/x-pack/test/functional/apps/visualize/reporting.ts +++ b/x-pack/test/functional/apps/visualize/reporting.ts @@ -40,6 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Print PDF button', () => { it('is not available if new', async () => { await PageObjects.common.navigateToUrl('visualize', 'new', { useActualUrl: true }); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch('ecommerce'); await PageObjects.reporting.openPdfReportingPanel(); diff --git a/x-pack/test/functional_vis_wizard/apps/index.ts b/x-pack/test/functional_vis_wizard/apps/index.ts new file mode 100644 index 00000000000000..730ef8e01fde9a --- /dev/null +++ b/x-pack/test/functional_vis_wizard/apps/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Visualization Wizard', function () { + this.tags('ciGroup4'); + + loadTestFile(require.resolve('./visualization_wizard')); + }); +} diff --git a/x-pack/test/functional_vis_wizard/apps/visualization_wizard.ts b/x-pack/test/functional_vis_wizard/apps/visualization_wizard.ts new file mode 100644 index 00000000000000..422b3a0d10fd25 --- /dev/null +++ b/x-pack/test/functional_vis_wizard/apps/visualization_wizard.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['visualize']); + + describe('lens and maps disabled', function () { + before(async function () { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('visualize/default'); + }); + + after(async function () { + await esArchiver.unload('logstash_functional'); + await esArchiver.unload('visualize/default'); + }); + + it('should not display lens and maps cards', async function () { + await PageObjects.visualize.navigateToNewVisualization(); + const expectedChartTypes = ['Custom visualization', 'TSVB']; + + // find all the chart types and make sure that maps and lens cards are not there + const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort(); + expect(chartTypes).to.eql(expectedChartTypes); + }); + }); +} diff --git a/x-pack/test/functional_vis_wizard/config.ts b/x-pack/test/functional_vis_wizard/config.ts new file mode 100644 index 00000000000000..f5ffb6996e0460 --- /dev/null +++ b/x-pack/test/functional_vis_wizard/config.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xPackFunctionalConfig = await readConfigFile(require.resolve('../functional/config')); + + return { + // default to the xpack functional config + ...xPackFunctionalConfig.getAll(), + testFiles: [require.resolve('./apps')], + junit: { + reportName: 'X-Pack Visualization Wizard Tests with Lens and Maps disabled', + }, + kbnTestServer: { + ...xPackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackFunctionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.lens.enabled=false', + '--xpack.maps.enabled=false', + ], + }, + }; +} diff --git a/x-pack/test/functional_vis_wizard/ftr_provider_context.d.ts b/x-pack/test/functional_vis_wizard/ftr_provider_context.d.ts new file mode 100644 index 00000000000000..b80934b5c0e9da --- /dev/null +++ b/x-pack/test/functional_vis_wizard/ftr_provider_context.d.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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { pageObjects }; From 651345b18b662dcf5df57dc979d8b9ab7ee555f0 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 6 Nov 2020 17:05:34 +0100 Subject: [PATCH 31/81] [Lens] Expose active data in some places (#79851) --- ...ressions-public.reactexpressionrenderer.md | 2 +- ...ons-public.reactexpressionrendererprops.md | 1 + ...ic.reactexpressionrendererprops.ondata_.md | 11 ++++ src/plugins/expressions/public/public.api.md | 4 +- .../public/react_expression_renderer.test.tsx | 32 ++++++++++ .../public/react_expression_renderer.tsx | 9 +++ .../editor_frame/config_panel/layer_panel.tsx | 3 + .../editor_frame/editor_frame.tsx | 1 + .../editor_frame/state_management.ts | 11 ++++ .../editor_frame/suggestion_helpers.ts | 6 +- .../editor_frame/suggestion_panel.tsx | 1 + .../workspace_panel/chart_switch.tsx | 1 + .../workspace_panel/workspace_panel.test.tsx | 42 ++++++++++++++ .../workspace_panel/workspace_panel.tsx | 17 ++++++ .../editor_frame_service/merge_tables.test.ts | 58 +++++++++++-------- .../editor_frame_service/merge_tables.ts | 18 +++++- .../lens/public/editor_frame_service/types.ts | 12 ++++ x-pack/plugins/lens/public/types.ts | 13 ++++- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 20 files changed, 211 insertions(+), 33 deletions(-) create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md create mode 100644 x-pack/plugins/lens/public/editor_frame_service/types.ts diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md index 32a7151578658c..8cc32ff698b386 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md @@ -7,5 +7,5 @@ Signature: ```typescript -ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element +ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md index e4980ce04b9e27..92ea071b23dfce 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md @@ -18,6 +18,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams | [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | string[] | | | [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | number | | | [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | string | ExpressionAstExpression | | +| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void | | | [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | (event: ExpressionRendererEvent) => void | | | [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | 'xs' | 's' | 'm' | 'l' | 'xl' | | | [reload$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.reload_.md) | Observable<unknown> | An observable which can be used to re-run the expression without destroying the component | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md new file mode 100644 index 00000000000000..05ddb0b13a5bee --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ReactExpressionRendererProps](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) > [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) + +## ReactExpressionRendererProps.onData$ property + +Signature: + +```typescript +onData$?: (data: TData, adapters?: TInspectorAdapters) => void; +``` diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index e6bff703aadcd3..773d61ebe2e28a 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -1057,7 +1057,7 @@ export interface Range { // Warning: (ae-missing-release-tag) "ReactExpressionRenderer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element; +export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element; // Warning: (ae-missing-release-tag) "ReactExpressionRendererProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1072,6 +1072,8 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { // (undocumented) expression: string | ExpressionAstExpression; // (undocumented) + onData$?: (data: TData, adapters?: TInspectorAdapters) => void; + // (undocumented) onEvent?: (event: ExpressionRendererEvent) => void; // (undocumented) padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 052c2a9f6a24a1..e52d4d153882ff 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -195,6 +195,38 @@ describe('ExpressionRenderer', () => { expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(0); }); + it('should call onData$ prop on every data$ observable emission in loader', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + + const newData = {}; + const inspectData = {}; + const onData$ = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$, + loading$: new Subject(), + events$: new Subject(), + update: jest.fn(), + inspect: jest.fn(() => inspectData), + }; + }); + + mount(); + + expect(onData$).toHaveBeenCalledTimes(0); + + act(() => { + dataSubject.next(newData); + }); + + expect(onData$).toHaveBeenCalledTimes(1); + expect(onData$.mock.calls[0][0]).toBe(newData); + expect(onData$.mock.calls[0][1]).toBe(inspectData); + }); + it('should fire onEvent prop on every events$ observable emission in loader', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index fecebf36ab7e6a..894325c8b65f7d 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -41,6 +41,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { ) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; + onData$?: (data: TData, adapters?: TInspectorAdapters) => void; /** * An observable which can be used to re-run the expression without destroying the component */ @@ -71,6 +72,7 @@ export const ReactExpressionRenderer = ({ renderError, expression, onEvent, + onData$, reload$, debounce, ...expressionLoaderOptions @@ -135,6 +137,13 @@ export const ReactExpressionRenderer = ({ }) ); } + if (onData$) { + subs.push( + expressionLoaderRef.current.data$.subscribe((newData) => { + onData$(newData, expressionLoaderRef.current?.inspect()); + }) + ); + } subs.push( expressionLoaderRef.current.loading$.subscribe(() => { hasHandledErrorRef.current = false; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 82f753e3520b05..c9d99bcfb6d8d8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -93,6 +93,7 @@ export function LayerPanel( state: props.visualizationState, frame: props.framePublicAPI, dateRange: props.framePublicAPI.dateRange, + activeData: props.framePublicAPI.activeData, }; const datasourceId = datasourcePublicAPI.datasourceId; const layerDatasourceState = props.datasourceStates[datasourceId].state; @@ -111,6 +112,7 @@ export function LayerPanel( ...layerDatasourceDropProps, frame: props.framePublicAPI, dateRange: props.framePublicAPI.dateRange, + activeData: props.framePublicAPI.activeData, }; const { groups } = activeVisualization.getConfiguration(layerVisualizationConfigProps); @@ -140,6 +142,7 @@ export function LayerPanel( nativeProps={{ layerId, state: layerDatasourceState, + activeData: props.framePublicAPI.activeData, setState: (updater: unknown) => { const newState = typeof updater === 'function' ? updater(layerDatasourceState) : updater; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index bb40b9f31d2546..935d65bfb6c08b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -101,6 +101,7 @@ export function EditorFrame(props: EditorFrameProps) { const framePublicAPI: FramePublicAPI = { datasourceLayers, + activeData: state.activeData, dateRange: props.dateRange, query: props.query, filters: props.filters, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts index fc8daaed059ddf..e0101493b27aab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts @@ -6,6 +6,7 @@ import { EditorFrameProps } from './index'; import { Document } from '../../persistence/saved_object_store'; +import { TableInspectorAdapter } from '../types'; export interface PreviewState { visualization: { @@ -21,6 +22,7 @@ export interface EditorFrameState extends PreviewState { description?: string; stagedPreview?: PreviewState; activeDatasourceId: string | null; + activeData?: TableInspectorAdapter; } export type Action = @@ -32,6 +34,10 @@ export type Action = type: 'UPDATE_TITLE'; title: string; } + | { + type: 'UPDATE_ACTIVE_DATA'; + tables: TableInspectorAdapter; + } | { type: 'UPDATE_STATE'; // Just for diagnostics, so we can determine what action @@ -139,6 +145,11 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta return { ...state, title: action.title }; case 'UPDATE_STATE': return action.updater(state); + case 'UPDATE_ACTIVE_DATA': + return { + ...state, + activeData: action.tables, + }; case 'UPDATE_LAYER': return { ...state, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index 95057f9db7e931..daaf893f2a703d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -7,6 +7,7 @@ import _ from 'lodash'; import { Ast } from '@kbn/interpreter/common'; import { IconType } from '@elastic/eui/src/components/icon/icon'; +import { Datatable } from 'src/plugins/expressions'; import { PaletteOutput } from 'src/plugins/charts/public'; import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; import { @@ -50,6 +51,7 @@ export function getSuggestions({ visualizationState, field, visualizeTriggerFieldContext, + activeData, mainPalette, }: { datasourceMap: Record; @@ -66,6 +68,7 @@ export function getSuggestions({ visualizationState: unknown; field?: unknown; visualizeTriggerFieldContext?: VisualizeFieldContext; + activeData?: Record; mainPalette?: PaletteOutput; }): Suggestion[] { const datasources = Object.entries(datasourceMap).filter( @@ -87,7 +90,8 @@ export function getSuggestions({ dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(datasourceState, field); } else { dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState( - datasourceState + datasourceState, + activeData ); } return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId })); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 201c91ee916764..2e24b64ecca26a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -188,6 +188,7 @@ export function SuggestionPanel({ visualizationMap, activeVisualizationId: currentVisualizationId, visualizationState: currentVisualizationState, + activeData: frame.activeData, }) .filter((suggestion) => !suggestion.hide) .filter( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index fe8747de667a3d..659626149aef26 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -325,6 +325,7 @@ function getTopSuggestion( activeVisualizationId: props.visualizationId, visualizationState: props.visualizationState, subVisualizationId, + activeData: props.framePublicAPI.activeData, mainPalette, }); const suggestions = unfilteredSuggestions.filter((suggestion) => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 447e94d09cdb2f..231c38ea540484 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -253,6 +253,48 @@ describe('workspace_panel', () => { expect(trigger.exec).toHaveBeenCalledWith({ data: eventData }); }); + it('should push add current data table to state on data$ emitting value', () => { + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + mockDatasource.toExpression.mockReturnValue('datasource'); + mockDatasource.getLayers.mockReturnValue(['first']); + const dispatch = jest.fn(); + + instance = mount( + 'vis' }, + }} + visualizationState={{}} + dispatch={dispatch} + ExpressionRenderer={expressionRendererMock} + core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock, data: dataMock }} + /> + ); + + const onData = expressionRendererMock.mock.calls[0][0].onData$!; + + const tableData = { table1: { columns: [], rows: [] } }; + onData(undefined, { tables: tableData }); + + expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_ACTIVE_DATA', tables: tableData }); + }); + it('should include data fetching for each layer in the expression', () => { const mockDatasource2 = createMockDatasource('a'); const framePublicAPI = createMockFramePublicAPI(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 5a6e9af5d6ff2e..e0dd3b3fe01ae9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -50,6 +50,7 @@ import { } from '../../../../../../../src/plugins/data/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { DropIllustration } from '../../../assets/drop_illustration'; +import { LensInspectorAdapters } from '../../types'; import { getOriginalRequestErrorMessage } from '../../error_helper'; import { validateDatasourceAndVisualization } from '../state_helpers'; @@ -296,6 +297,7 @@ export function WorkspacePanel({ expression={expression} framePublicAPI={framePublicAPI} timefilter={plugins.data.query.timefilter.timefilter} + dispatch={dispatch} onEvent={onEvent} setLocalState={setLocalState} localState={{ ...localState, configurationValidationError }} @@ -339,11 +341,13 @@ export const InnerVisualizationWrapper = ({ setLocalState, localState, ExpressionRendererComponent, + dispatch, }: { expression: Ast | null | undefined; framePublicAPI: FramePublicAPI; timefilter: TimefilterContract; onEvent: (event: ExpressionRendererEvent) => void; + dispatch: (action: Action) => void; setLocalState: (dispatch: (prevState: WorkspaceState) => WorkspaceState) => void; localState: WorkspaceState & { configurationValidationError?: Array<{ shortMessage: string; longMessage: string }>; @@ -369,6 +373,18 @@ export const InnerVisualizationWrapper = ({ ] ); + const onData$ = useCallback( + (data: unknown, inspectorAdapters?: LensInspectorAdapters) => { + if (inspectorAdapters && inspectorAdapters.tables) { + dispatch({ + type: 'UPDATE_ACTIVE_DATA', + tables: inspectorAdapters.tables, + }); + } + }, + [dispatch] + ); + if (localState.configurationValidationError) { let showExtraErrors = null; if (localState.configurationValidationError.length > 1) { @@ -455,6 +471,7 @@ export const InnerVisualizationWrapper = ({ searchContext={context} reload$={autoRefreshFetch$} onEvent={onEvent} + onData$={onData$} renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => { const visibleErrorMessage = getOriginalRequestErrorMessage(error) || errorMessage; diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts index 5afabb9a52367a..07c16665d11b44 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts @@ -6,34 +6,35 @@ import moment from 'moment'; import { mergeTables } from './merge_tables'; -import { Datatable } from 'src/plugins/expressions'; +import { Datatable, ExecutionContext } from 'src/plugins/expressions'; +import { LensInspectorAdapters } from './types'; describe('lens_merge_tables', () => { - it('should produce a row with the nested table as defined', () => { - const sampleTable1: Datatable = { - type: 'datatable', - columns: [ - { id: 'bucket', name: 'A', meta: { type: 'string' } }, - { id: 'count', name: 'Count', meta: { type: 'number' } }, - ], - rows: [ - { bucket: 'a', count: 5 }, - { bucket: 'b', count: 10 }, - ], - }; + const sampleTable1: Datatable = { + type: 'datatable', + columns: [ + { id: 'bucket', name: 'A', meta: { type: 'string' } }, + { id: 'count', name: 'Count', meta: { type: 'number' } }, + ], + rows: [ + { bucket: 'a', count: 5 }, + { bucket: 'b', count: 10 }, + ], + }; - const sampleTable2: Datatable = { - type: 'datatable', - columns: [ - { id: 'bucket', name: 'C', meta: { type: 'string' } }, - { id: 'avg', name: 'Average', meta: { type: 'number' } }, - ], - rows: [ - { bucket: 'a', avg: 2.5 }, - { bucket: 'b', avg: 9 }, - ], - }; + const sampleTable2: Datatable = { + type: 'datatable', + columns: [ + { id: 'bucket', name: 'C', meta: { type: 'string' } }, + { id: 'avg', name: 'Average', meta: { type: 'number' } }, + ], + rows: [ + { bucket: 'a', avg: 2.5 }, + { bucket: 'b', avg: 9 }, + ], + }; + it('should produce a row with the nested table as defined', () => { expect( mergeTables.fn( null, @@ -47,6 +48,15 @@ describe('lens_merge_tables', () => { }); }); + it('should store the current tables in the tables inspector', () => { + const adapters: LensInspectorAdapters = { tables: {} }; + mergeTables.fn(null, { layerIds: ['first', 'second'], tables: [sampleTable1, sampleTable2] }, { + inspectorAdapters: adapters, + } as ExecutionContext); + expect(adapters.tables!.first).toBe(sampleTable1); + expect(adapters.tables!.second).toBe(sampleTable2); + }); + it('should pass the date range along', () => { expect( mergeTables.fn( diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts index e4f7b07084ea97..03ef7cf9cc6374 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { + ExecutionContext, Datatable, ExpressionFunctionDefinition, ExpressionValueSearchContext, @@ -14,6 +15,7 @@ import { search } from '../../../../../src/plugins/data/public'; const { toAbsoluteDates } = search.aggs; import { LensMultiTable } from '../types'; +import { LensInspectorAdapters } from './types'; interface MergeTables { layerIds: string[]; @@ -24,12 +26,14 @@ export const mergeTables: ExpressionFunctionDefinition< 'lens_merge_tables', ExpressionValueSearchContext | null, MergeTables, - LensMultiTable + LensMultiTable, + ExecutionContext > = { name: 'lens_merge_tables', type: 'lens_multitable', help: i18n.translate('xpack.lens.functions.mergeTables.help', { - defaultMessage: 'A helper to merge any number of kibana tables into a single table', + defaultMessage: + 'A helper to merge any number of kibana tables into a single table and expose it via inspector adapter', }), args: { layerIds: { @@ -44,10 +48,18 @@ export const mergeTables: ExpressionFunctionDefinition< }, }, inputTypes: ['kibana_context', 'null'], - fn(input, { layerIds, tables }) { + fn(input, { layerIds, tables }, context) { + if (!context.inspectorAdapters) { + context.inspectorAdapters = {}; + } + if (!context.inspectorAdapters.tables) { + context.inspectorAdapters.tables = {}; + } const resultTables: Record = {}; tables.forEach((table, index) => { resultTables[layerIds[index]] = table; + // adapter is always defined at that point because we make sure by the beginning of the function + context.inspectorAdapters.tables![layerIds[index]] = table; }); return { type: 'lens_multitable', diff --git a/x-pack/plugins/lens/public/editor_frame_service/types.ts b/x-pack/plugins/lens/public/editor_frame_service/types.ts new file mode 100644 index 00000000000000..2da95ec2fd66f6 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/types.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 { Datatable } from 'src/plugins/expressions'; + +export type TableInspectorAdapter = Record; +export interface LensInspectorAdapters { + tables?: TableInspectorAdapter; +} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 27ab8f258bba85..3c96579fdc9431 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -178,7 +178,10 @@ export interface Datasource { indexPatternId: string, fieldName: string ) => Array>; - getDatasourceSuggestionsFromCurrentState: (state: T) => Array>; + getDatasourceSuggestionsFromCurrentState: ( + state: T, + activeData?: Record + ) => Array>; getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI; getErrorMessages: (state: T) => Array<{ shortMessage: string; longMessage: string }> | undefined; @@ -231,6 +234,7 @@ export type DatasourceDimensionProps = SharedDimensionProps & { columnId: string; onRemove?: (accessor: string) => void; state: T; + activeData?: Record; }; // The only way a visualization has to restrict the query building @@ -249,6 +253,7 @@ export interface DatasourceLayerPanelProps { layerId: string; state: T; setState: StateSetter; + activeData?: Record; } export interface DraggedOperation { @@ -428,6 +433,12 @@ export interface VisualizationSuggestion { export interface FramePublicAPI { datasourceLayers: Record; + /** + * Data of the chart currently rendered in the preview. + * This data might be not available (e.g. if the chart can't be rendered) or outdated and belonging to another chart. + * If accessing, make sure to check whether expected columns actually exist. + */ + activeData?: Record; dateRange: DateRange; query: Query; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 595c925f825bdd..85f0290cb5b1d4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10744,7 +10744,6 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "次へ", "xpack.lens.fittingFunctionsTitle.none": "非表示", "xpack.lens.fittingFunctionsTitle.zero": "ゼロ", - "xpack.lens.functions.mergeTables.help": "いくつかの Kibana 表を 1 つの表に結合するのをアシストします", "xpack.lens.functions.renameColumns.help": "データベースの列の名前の変更をアシストします", "xpack.lens.functions.renameColumns.idMap.help": "キーが古い列 ID で値が対応する新しい列 ID となるように JSON エンコーディングされたオブジェクトです。他の列 ID はすべてのそのままです。", "xpack.lens.includeValueButtonAriaLabel": "{value}を含める", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index bdcb3b8b207814..59399d9278aa0d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10757,7 +10757,6 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "下一", "xpack.lens.fittingFunctionsTitle.none": "隐藏", "xpack.lens.fittingFunctionsTitle.zero": "零", - "xpack.lens.functions.mergeTables.help": "将任何数目的 kibana 表合并成单个表的助手", "xpack.lens.functions.renameColumns.help": "用于重命名数据表列的助手", "xpack.lens.functions.renameColumns.idMap.help": "旧列 ID 为键且相应新列 ID 为值的 JSON 编码对象。所有其他列 ID 都将保留。", "xpack.lens.includeValueButtonAriaLabel": "包括 {value}", From 7904ee0ceb7c9219d5fe3816e90239fefb8945a9 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Fri, 6 Nov 2020 17:07:44 +0100 Subject: [PATCH 32/81] Add support for provider specific session timeout settings. (#82583) --- docs/settings/security-settings.asciidoc | 45 +- .../common/model/authenticated_user.mock.ts | 2 +- .../common/model/authenticated_user.test.ts | 2 + .../common/model/authenticated_user.ts | 5 +- .../server/authentication/authenticator.ts | 36 +- .../server/authentication/providers/base.ts | 2 +- .../authentication/providers/http.test.ts | 5 +- .../authentication/providers/kerberos.test.ts | 8 +- .../authentication/providers/oidc.test.ts | 2 +- .../authentication/providers/pki.test.ts | 10 +- .../authentication/providers/saml.test.ts | 6 +- .../authentication/providers/token.test.ts | 6 +- x-pack/plugins/security/server/config.test.ts | 547 +++++++++++++++++- x-pack/plugins/security/server/config.ts | 54 +- .../routes/users/change_password.test.ts | 2 +- .../server/session_management/session.ts | 86 ++- .../session_management/session_index.test.ts | 290 +++++++++- .../session_management/session_index.ts | 89 +-- .../apis/security/basic_login.js | 8 +- .../apis/security/kerberos_login.ts | 6 +- .../apis/authorization_code_flow/oidc_auth.ts | 6 +- .../apis/implicit_flow/oidc_auth.ts | 2 +- .../apis/security/pki_auth.ts | 8 +- .../session_idle.config.ts | 29 +- .../session_lifespan.config.ts | 29 +- .../login_selector/basic_functionality.ts | 118 ++-- .../tests/saml/saml_login.ts | 4 +- .../tests/session_idle/cleanup.ts | 122 +++- .../tests/session_idle/extension.ts | 4 +- .../tests/session_lifespan/cleanup.ts | 106 +++- 30 files changed, 1371 insertions(+), 268 deletions(-) diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 6b01094f7248a0..12043ead28d555 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -92,8 +92,8 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend `..icon` {ess-icon} | Custom icon for the provider entry displayed on the Login Selector UI. -| `xpack.security.authc.providers.` -`..showInSelector` {ess-icon} +| `xpack.security.authc.providers..` +`.showInSelector` {ess-icon} | Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. 2+a| @@ -103,10 +103,31 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend You are unable to set this setting to `false` for `basic` and `token` authentication providers. ============ -| `xpack.security.authc.providers.` -`..accessAgreement.message` {ess-icon} +| `xpack.security.authc.providers..` +`.accessAgreement.message` {ess-icon} | Access agreement text in Markdown format. For more information, refer to <>. +| [[xpack-security-provider-session-idleTimeout]] `xpack.security.authc.providers..` +`.session.idleTimeout` {ess-icon} +| Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>. + +2+a| +[TIP] +============ +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +============ + +| [[xpack-security-provider-session-lifespan]] `xpack.security.authc.providers..` +`.session.lifespan` {ess-icon} +| Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If +this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>. + +2+a| +[TIP] +============ +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +============ + |=== [float] @@ -210,32 +231,32 @@ You can configure the following settings in the `kibana.yml` file. |[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} | Ensures that user sessions will expire after a period of inactivity. This and <> are both -highly recommended. By default, this setting is not set. +highly recommended. You can also specify this setting for <>. If this is _not_ set or set to `0`, then sessions will never expire due to inactivity. By default, this setting is not set. 2+a| [TIP] ============ -The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ |[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} - | Ensures that user sessions will expire after the defined time period. This behavior also known as an "absolute timeout". If -this is _not_ set, user sessions could stay active indefinitely. This and <> are both highly -recommended. By default, this setting is not set. + | Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If +this is _not_ set or set to `0`, user sessions could stay active indefinitely. This and <> are both highly +recommended. You can also specify this setting for <>. By default, this setting is not set. 2+a| [TIP] ============ -The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ -| `xpack.security.session.cleanupInterval` +| `xpack.security.session.cleanupInterval` {ess-icon} | Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds. 2+a| [TIP] ============ -The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ |=== diff --git a/x-pack/plugins/security/common/model/authenticated_user.mock.ts b/x-pack/plugins/security/common/model/authenticated_user.mock.ts index 0393c94da8d406..d9947bc96b78b8 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.mock.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.mock.ts @@ -15,7 +15,7 @@ export function mockAuthenticatedUser(user: Partial = {}) { enabled: true, authentication_realm: { name: 'native1', type: 'native' }, lookup_realm: { name: 'native1', type: 'native' }, - authentication_provider: 'basic1', + authentication_provider: { type: 'basic', name: 'basic1' }, authentication_type: 'realm', ...user, }; diff --git a/x-pack/plugins/security/common/model/authenticated_user.test.ts b/x-pack/plugins/security/common/model/authenticated_user.test.ts index cdf1423df56df8..d253fed97f353e 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.test.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.test.ts @@ -12,6 +12,7 @@ describe('#canUserChangePassword', () => { expect( canUserChangePassword({ username: 'foo', + authentication_provider: { type: 'basic', name: 'basic1' }, authentication_realm: { name: 'the realm name', type: realm, @@ -25,6 +26,7 @@ describe('#canUserChangePassword', () => { expect( canUserChangePassword({ username: 'foo', + authentication_provider: { type: 'the provider type', name: 'does not matter' }, authentication_realm: { name: 'the realm name', type: 'does not matter', diff --git a/x-pack/plugins/security/common/model/authenticated_user.ts b/x-pack/plugins/security/common/model/authenticated_user.ts index 5ea420af202dc2..d5c8d4e474c601 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import type { AuthenticationProvider } from '../types'; import { User } from './user'; const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native']; @@ -28,9 +29,9 @@ export interface AuthenticatedUser extends User { lookup_realm: UserRealm; /** - * Name of the Kibana authentication provider that used to authenticate user. + * The authentication provider that used to authenticate user. */ - authentication_provider: string; + authentication_provider: AuthenticationProvider; /** * The AuthenticationType used by ES to authenticate the user. diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 80aeb4f8b29598..eef45598d1761c 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -10,14 +10,14 @@ import { ILegacyClusterClient, IBasePath, } from '../../../../../src/core/server'; -import { SecurityLicense } from '../../common/licensing'; -import { AuthenticatedUser } from '../../common/model'; -import { AuthenticationProvider } from '../../common/types'; +import type { SecurityLicense } from '../../common/licensing'; +import type { AuthenticatedUser } from '../../common/model'; +import type { AuthenticationProvider } from '../../common/types'; import { SecurityAuditLogger, AuditServiceSetup, userLoginEvent } from '../audit'; -import { ConfigType } from '../config'; +import type { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; -import { SecurityFeatureUsageServiceStart } from '../feature_usage'; -import { SessionValue, Session } from '../session_management'; +import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; +import type { SessionValue, Session } from '../session_management'; import { AuthenticationProviderOptions, @@ -259,7 +259,7 @@ export class Authenticator { isLoginAttemptWithProviderName(attempt) && this.providers.has(attempt.provider.name) ? [[attempt.provider.name, this.providers.get(attempt.provider.name)!]] : isLoginAttemptWithProviderType(attempt) - ? [...this.providerIterator(existingSessionValue)].filter( + ? [...this.providerIterator(existingSessionValue?.provider.name)].filter( ([, { type }]) => type === attempt.provider.type ) : []; @@ -338,7 +338,9 @@ export class Authenticator { ); } - for (const [providerName, provider] of this.providerIterator(existingSessionValue)) { + for (const [providerName, provider] of this.providerIterator( + existingSessionValue?.provider.name + )) { // Check if current session has been set by this provider. const ownsSession = existingSessionValue?.provider.name === providerName && @@ -395,7 +397,7 @@ export class Authenticator { // active session already some providers can still properly respond to the 3rd-party logout // request. For example SAML provider can process logout request encoded in `SAMLRequest` // query string parameter. - for (const [, provider] of this.providerIterator(null)) { + for (const [, provider] of this.providerIterator()) { const deauthenticationResult = await provider.logout(request); if (!deauthenticationResult.notHandled()) { return deauthenticationResult; @@ -473,22 +475,22 @@ export class Authenticator { } /** - * Returns provider iterator where providers are sorted in the order of priority (based on the session ownership). - * @param sessionValue Current session value. + * Returns provider iterator starting from the suggested provider if any. + * @param suggestedProviderName Optional name of the provider to return first. */ private *providerIterator( - sessionValue: SessionValue | null + suggestedProviderName?: string | null ): IterableIterator<[string, BaseAuthenticationProvider]> { - // If there is no session to predict which provider to use first, let's use the order - // providers are configured in. Otherwise return provider that owns session first, and only then the rest + // If there is no provider suggested or suggested provider isn't configured, let's use the order + // providers are configured in. Otherwise return suggested provider first, and only then the rest // of providers. - if (!sessionValue) { + if (!suggestedProviderName || !this.providers.has(suggestedProviderName)) { yield* this.providers; } else { - yield [sessionValue.provider.name, this.providers.get(sessionValue.provider.name)!]; + yield [suggestedProviderName, this.providers.get(suggestedProviderName)!]; for (const [providerName, provider] of this.providers) { - if (providerName !== sessionValue.provider.name) { + if (providerName !== suggestedProviderName) { yield [providerName, provider]; } } diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index 6b13c32a2f1ed7..030b2a6e968afb 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -113,7 +113,7 @@ export abstract class BaseAuthenticationProvider { ...(await this.options.client .asScoped({ headers: { ...request.headers, ...authHeaders } }) .callAsCurrentUser('shield.authenticate')), - authentication_provider: this.options.name, + authentication_provider: { type: this.type, name: this.options.name }, } as AuthenticatedUser); } } diff --git a/x-pack/plugins/security/server/authentication/providers/http.test.ts b/x-pack/plugins/security/server/authentication/providers/http.test.ts index c221ecd3f1e20f..512a8ead2c32b0 100644 --- a/x-pack/plugins/security/server/authentication/providers/http.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/http.test.ts @@ -136,7 +136,10 @@ describe('HTTPAuthenticationProvider', () => { }); await expect(provider.authenticate(request)).resolves.toEqual( - AuthenticationResult.succeeded({ ...user, authentication_provider: 'http' }) + AuthenticationResult.succeeded({ + ...user, + authentication_provider: { type: 'http', name: 'http' }, + }) ); expectAuthenticateCall(mockOptions.client, { headers: { authorization: header } }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index ee89af10cbebaf..4ea395e7b53de4 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -128,7 +128,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' }, @@ -164,7 +164,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer some-token' }, authResponseHeaders: { 'WWW-Authenticate': 'Negotiate response-token' }, @@ -361,7 +361,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization } } ) ); @@ -401,7 +401,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer newfoo' }, state: { accessToken: 'newfoo', refreshToken: 'newbar' }, diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index fe46b159833bc8..dfea7e508b3333 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -29,7 +29,7 @@ describe('OIDCAuthenticationProvider', () => { mockOptions = mockAuthenticationProviderOptions({ name: 'oidc' }); mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockUser = mockAuthenticatedUser({ authentication_provider: 'oidc' }); + mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'oidc', name: 'oidc' } }); mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => { if (method === 'shield.authenticate') { return mockUser; diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 7e76f4c81998d8..969682b225ceb4 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -127,7 +127,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -169,7 +169,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -356,7 +356,7 @@ describe('PKIAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -405,7 +405,7 @@ describe('PKIAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -486,7 +486,7 @@ describe('PKIAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: `Bearer ${state.accessToken}` } } ) ); diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index d160b79bf659d5..a1f2e99c133577 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -28,7 +28,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions = mockAuthenticationProviderOptions({ name: 'saml' }); mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockUser = mockAuthenticatedUser({ authentication_provider: 'saml' }); + mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } }); mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => { if (method === 'shield.authenticate') { return mockUser; @@ -490,7 +490,9 @@ describe('SAMLAuthenticationProvider', () => { for (const [description, response] of [ [ 'current session is valid', - Promise.resolve(mockAuthenticatedUser({ authentication_provider: 'saml' })), + Promise.resolve( + mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } }) + ), ], [ 'current session is is expired', diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 8cd11bce982691..4501004ab69c17 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -60,7 +60,7 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.login(request, credentials)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'token' }, + { ...user, authentication_provider: { type: 'token', name: 'token' } }, { authHeaders: { authorization }, state: tokenPair } ) ); @@ -196,7 +196,7 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'token' }, + { ...user, authentication_provider: { type: 'token', name: 'token' } }, { authHeaders: { authorization } } ) ); @@ -236,7 +236,7 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'token' }, + { ...user, authentication_provider: { type: 'token', name: 'token' } }, { authHeaders: { authorization: 'Bearer newfoo' }, state: { accessToken: 'newfoo', refreshToken: 'newbar' }, diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 32b8708d2b3814..e75c0d1c4085fe 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -36,6 +36,10 @@ describe('config schema', () => { "hint": undefined, "icon": undefined, "order": 0, + "session": Object { + "idleTimeout": undefined, + "lifespan": undefined, + }, "showInSelector": true, }, }, @@ -54,8 +58,6 @@ describe('config schema', () => { "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", - "idleTimeout": null, - "lifespan": null, }, } `); @@ -82,6 +84,10 @@ describe('config schema', () => { "hint": undefined, "icon": undefined, "order": 0, + "session": Object { + "idleTimeout": undefined, + "lifespan": undefined, + }, "showInSelector": true, }, }, @@ -100,8 +106,6 @@ describe('config schema', () => { "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", - "idleTimeout": null, - "lifespan": null, }, } `); @@ -128,6 +132,10 @@ describe('config schema', () => { "hint": undefined, "icon": undefined, "order": 0, + "session": Object { + "idleTimeout": undefined, + "lifespan": undefined, + }, "showInSelector": true, }, }, @@ -145,8 +153,6 @@ describe('config schema', () => { "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", - "idleTimeout": null, - "lifespan": null, }, } `); @@ -387,6 +393,35 @@ describe('config schema', () => { "enabled": true, "icon": "logoElasticsearch", "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "basic": Object { + "basic1": Object { + "description": "Log in with Elasticsearch", + "enabled": true, + "icon": "logoElasticsearch", + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -439,6 +474,35 @@ describe('config schema', () => { "enabled": true, "icon": "logoElasticsearch", "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + token: { token1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "token": Object { + "token1": Object { + "description": "Log in with Elasticsearch", + "enabled": true, + "icon": "logoElasticsearch", + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -477,6 +541,33 @@ describe('config schema', () => { "pki1": Object { "enabled": true, "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + pki: { pki1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "pki": Object { + "pki1": Object { + "enabled": true, + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -517,6 +608,33 @@ describe('config schema', () => { "kerberos1": Object { "enabled": true, "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + kerberos: { kerberos1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "kerberos": Object { + "kerberos1": Object { + "enabled": true, + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -562,12 +680,53 @@ describe('config schema', () => { "enabled": true, "order": 0, "realm": "oidc1", + "session": Object {}, "showInSelector": true, }, "oidc2": Object { "enabled": true, "order": 1, "realm": "oidc2", + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + oidc: { + oidc1: { order: 0, realm: 'oidc1', session: { idleTimeout: 123 } }, + oidc2: { order: 1, realm: 'oidc2', session: { idleTimeout: 321, lifespan: 546 } }, + }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "oidc": Object { + "oidc1": Object { + "enabled": true, + "order": 0, + "realm": "oidc1", + "session": Object { + "idleTimeout": "PT0.123S", + }, + "showInSelector": true, + }, + "oidc2": Object { + "enabled": true, + "order": 1, + "realm": "oidc2", + "session": Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -617,6 +776,62 @@ describe('config schema', () => { "enabled": true, "order": 0, "realm": "saml1", + "session": Object {}, + "showInSelector": true, + "useRelayStateDeepLink": false, + }, + "saml2": Object { + "enabled": true, + "maxRedirectURLSize": ByteSizeValue { + "valueInBytes": 1024, + }, + "order": 1, + "realm": "saml2", + "session": Object {}, + "showInSelector": true, + "useRelayStateDeepLink": false, + }, + "saml3": Object { + "enabled": true, + "order": 2, + "realm": "saml3", + "session": Object {}, + "showInSelector": true, + "useRelayStateDeepLink": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + saml: { + saml1: { order: 0, realm: 'saml1', session: { idleTimeout: 123 } }, + saml2: { + order: 1, + realm: 'saml2', + maxRedirectURLSize: '1kb', + session: { idleTimeout: 321, lifespan: 546 }, + }, + saml3: { order: 2, realm: 'saml3', useRelayStateDeepLink: true }, + }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "saml": Object { + "saml1": Object { + "enabled": true, + "order": 0, + "realm": "saml1", + "session": Object { + "idleTimeout": "PT0.123S", + }, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -627,6 +842,10 @@ describe('config schema', () => { }, "order": 1, "realm": "saml2", + "session": Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.546S", + }, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -634,6 +853,7 @@ describe('config schema', () => { "enabled": true, "order": 2, "realm": "saml3", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": true, }, @@ -701,6 +921,7 @@ describe('config schema', () => { "enabled": true, "icon": "logoElasticsearch", "order": 0, + "session": Object {}, "showInSelector": true, }, "basic2": Object { @@ -708,6 +929,7 @@ describe('config schema', () => { "enabled": false, "icon": "logoElasticsearch", "order": 1, + "session": Object {}, "showInSelector": true, }, }, @@ -716,6 +938,7 @@ describe('config schema', () => { "enabled": false, "order": 3, "realm": "saml3", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -723,6 +946,7 @@ describe('config schema', () => { "enabled": true, "order": 1, "realm": "saml1", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -730,6 +954,7 @@ describe('config schema', () => { "enabled": true, "order": 2, "realm": "saml2", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -1089,4 +1314,314 @@ describe('createConfig()', () => { '[audit]: xpack.security.audit.ignore_filters can only be used with the ECS audit logger. To enable the ECS audit logger, specify where you want to write the audit events using xpack.security.audit.appender.' ); }); + + describe('#getExpirationTimeouts', () => { + function createMockConfig(config: Record = {}) { + return createConfig(ConfigSchema.validate(config), loggingSystemMock.createLogger(), { + isTLSEnabled: false, + }); + } + + it('returns default values if neither global nor provider specific settings are set', async () => { + expect(createMockConfig().session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + }); + + it('correctly handles explicitly disabled global settings', async () => { + expect( + createMockConfig({ + session: { idleTimeout: null, lifespan: null }, + }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + + expect( + createMockConfig({ + session: { idleTimeout: 0, lifespan: 0 }, + }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + }); + + it('falls back to the global settings if provider does not override them', async () => { + expect( + createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({ + type: 'basic', + name: 'basic1', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": null, + } + `); + + expect( + createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({ + type: 'basic', + name: 'basic1', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.456S", + } + `); + + expect( + createMockConfig({ + session: { idleTimeout: 123, lifespan: 456 }, + }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.456S", + } + `); + }); + + it('falls back to the global settings if provider is not known', async () => { + expect( + createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({ + type: 'some type', + name: 'some name', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": null, + } + `); + + expect( + createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({ + type: 'some type', + name: 'some name', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.456S", + } + `); + + expect( + createMockConfig({ + session: { idleTimeout: 123, lifespan: 456 }, + }).session.getExpirationTimeouts({ type: 'some type', name: 'some name' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.456S", + } + `); + }); + + it('uses provider overrides if specified (only idle timeout)', async () => { + const configWithoutGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 332211 } } }, + }, + }, + session: { idleTimeout: null }, + }); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": null, + } + `); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": null, + } + `); + + const configWithGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 332211 } } }, + }, + }, + session: { idleTimeout: 123 }, + }); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": null, + } + `); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": null, + } + `); + }); + + it('uses provider overrides if specified (only lifespan)', async () => { + const configWithoutGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { lifespan: 654 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { lifespan: 665544 } } }, + }, + }, + session: { lifespan: null }, + }); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.654S", + } + `); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT11M5.544S", + } + `); + + const configWithGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { lifespan: 654 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 665544 } } }, + }, + }, + session: { lifespan: 456 }, + }); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.654S", + } + `); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT11M5.544S", + "lifespan": "PT0.456S", + } + `); + }); + + it('uses provider overrides if specified (both idle timeout and lifespan)', async () => { + const configWithoutGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321, lifespan: 654 } } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: 332211, lifespan: 665544 }, + }, + }, + }, + }, + session: { idleTimeout: null, lifespan: null }, + }); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.654S", + } + `); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": "PT11M5.544S", + } + `); + + const configWithGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321, lifespan: 654 } } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: 332211, lifespan: 665544 }, + }, + }, + }, + }, + session: { idleTimeout: 123, lifespan: 456 }, + }); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.654S", + } + `); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": "PT11M5.544S", + } + `); + }); + + it('uses provider overrides if disabled (both idle timeout and lifespan)', async () => { + const config = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: null, lifespan: null } } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: 0, lifespan: 0 }, + }, + }, + }, + }, + session: { idleTimeout: 123, lifespan: 456 }, + }); + expect(config.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + expect(config.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + }); + }); }); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 80b46a67ce0115..4da0a8598309a3 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -5,11 +5,24 @@ */ import crypto from 'crypto'; +import type { Duration } from 'moment'; import { schema, Type, TypeOf } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { Logger, config as coreConfig } from '../../../../src/core/server'; +import type { AuthenticationProvider } from '../common/types'; export type ConfigType = ReturnType; +type RawConfigType = TypeOf; + +interface ProvidersCommonConfigType { + enabled: Type; + showInSelector: Type; + order: Type; + description?: Type; + hint?: Type; + icon?: Type; + session?: Type<{ idleTimeout?: Duration | null; lifespan?: Duration | null }>; +} const providerOptionsSchema = (providerType: string, optionsSchema: Type) => schema.conditional( @@ -21,10 +34,6 @@ const providerOptionsSchema = (providerType: string, optionsSchema: Type) = schema.never() ); -type ProvidersCommonConfigType = Record< - 'enabled' | 'showInSelector' | 'order' | 'description' | 'hint' | 'icon', - Type ->; function getCommonProviderSchemaProperties(overrides: Partial = {}) { return { enabled: schema.boolean({ defaultValue: true }), @@ -34,6 +43,10 @@ function getCommonProviderSchemaProperties(overrides: Partial, + config: RawConfigType, logger: Logger, { isTLSEnabled }: { isTLSEnabled: boolean } ) { @@ -314,7 +328,33 @@ export function createConfig( sortedProviders: Object.freeze(sortedProviders), http: config.authc.http, }, + session: getSessionConfig(config.session, providers), encryptionKey, secureCookies, }; } + +function getSessionConfig(session: RawConfigType['session'], providers: ProvidersConfigType) { + return { + cleanupInterval: session.cleanupInterval, + getExpirationTimeouts({ type, name }: AuthenticationProvider) { + // Both idle timeout and lifespan from the provider specific session config can have three + // possible types of values: `Duration`, `null` and `undefined`. The `undefined` type means that + // provider doesn't override session config and we should fall back to the global one instead. + const providerSessionConfig = providers[type as keyof ProvidersConfigType]?.[name]?.session; + + const [idleTimeout, lifespan] = [ + [session.idleTimeout, providerSessionConfig?.idleTimeout], + [session.lifespan, providerSessionConfig?.lifespan], + ].map(([globalTimeout, providerTimeout]) => { + const timeout = providerTimeout === undefined ? globalTimeout ?? null : providerTimeout; + return timeout && timeout.asMilliseconds() > 0 ? timeout : null; + }); + + return { + idleTimeout, + lifespan, + }; + }, + }; +} diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index 5e0f0a6005fb20..c66b5f985cb33f 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -195,7 +195,7 @@ describe('Change password', () => { it('successfully changes own password if provided old password is correct for non-basic provider.', async () => { const mockUser = mockAuthenticatedUser({ username: 'user', - authentication_provider: 'token1', + authentication_provider: { type: 'token', name: 'token1' }, }); authc.getCurrentUser.mockReturnValue(mockUser); authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockUser)); diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index a85369a6f40323..757b1aaeddcbcf 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import nodeCrypto, { Crypto } from '@elastic/node-crypto'; -import type { PublicMethodsOf } from '@kbn/utility-types'; import { promisify } from 'util'; import { randomBytes, createHash } from 'crypto'; -import { Duration } from 'moment'; -import { KibanaRequest, Logger } from '../../../../../src/core/server'; -import { AuthenticationProvider } from '../../common/types'; -import { ConfigType } from '../config'; -import { SessionIndex, SessionIndexValue } from './session_index'; -import { SessionCookie } from './session_cookie'; +import nodeCrypto, { Crypto } from '@elastic/node-crypto'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { KibanaRequest, Logger } from '../../../../../src/core/server'; +import type { AuthenticationProvider } from '../../common/types'; +import type { ConfigType } from '../config'; +import type { SessionIndex, SessionIndexValue } from './session_index'; +import type { SessionCookie } from './session_cookie'; /** * The shape of the value that represents user's session information. @@ -86,21 +85,6 @@ const SID_BYTE_LENGTH = 32; const AAD_BYTE_LENGTH = 32; export class Session { - /** - * Session idle timeout in ms. If `null`, a session will stay active until its max lifespan is reached. - */ - private readonly idleTimeout: Duration | null; - - /** - * Timeout after which idle timeout property is updated in the index. - */ - private readonly idleIndexUpdateTimeout: number | null; - - /** - * Session max lifespan in ms. If `null` session may live indefinitely. - */ - private readonly lifespan: Duration | null; - /** * Used to encrypt and decrypt portion of the session value using configured encryption key. */ @@ -113,14 +97,6 @@ export class Session { constructor(private readonly options: Readonly) { this.crypto = nodeCrypto({ encryptionKey: this.options.config.encryptionKey }); - this.idleTimeout = this.options.config.session.idleTimeout; - this.lifespan = this.options.config.session.lifespan; - - // The timeout after which we update index is two times longer than configured idle timeout - // since index updates are costly and we want to minimize them. - this.idleIndexUpdateTimeout = this.options.config.session.idleTimeout - ? this.options.config.session.idleTimeout.asMilliseconds() * 2 - : null; } /** @@ -194,7 +170,7 @@ export class Session { const sessionLogger = this.getLoggerForSID(sid); sessionLogger.debug('Creating a new session.'); - const sessionExpirationInfo = this.calculateExpiry(); + const sessionExpirationInfo = this.calculateExpiry(sessionValue.provider); const { username, state, ...publicSessionValue } = sessionValue; // First try to store session in the index and only then in the cookie to make sure cookie is @@ -227,7 +203,10 @@ export class Session { return null; } - const sessionExpirationInfo = this.calculateExpiry(sessionCookieValue.lifespanExpiration); + const sessionExpirationInfo = this.calculateExpiry( + sessionValue.provider, + sessionCookieValue.lifespanExpiration + ); const { username, state, metadata, ...publicSessionInfo } = sessionValue; // First try to store session in the index and only then in the cookie to make sure cookie is @@ -276,7 +255,10 @@ export class Session { // We calculate actual expiration values based on the information extracted from the portion of // the session value that is stored in the cookie since it always contains the most recent value. - const sessionExpirationInfo = this.calculateExpiry(sessionCookieValue.lifespanExpiration); + const sessionExpirationInfo = this.calculateExpiry( + sessionValue.provider, + sessionCookieValue.lifespanExpiration + ); if ( sessionExpirationInfo.idleTimeoutExpiration === sessionValue.idleTimeoutExpiration && sessionExpirationInfo.lifespanExpiration === sessionValue.lifespanExpiration @@ -311,17 +293,24 @@ export class Session { 'Session lifespan configuration has changed, session index will be updated.' ); updateSessionIndex = true; - } else if ( - this.idleIndexUpdateTimeout !== null && - this.idleIndexUpdateTimeout < - sessionExpirationInfo.idleTimeoutExpiration! - - sessionValue.metadata.index.idleTimeoutExpiration! - ) { - // 3. If idle timeout was updated a while ago. - sessionLogger.debug( - 'Session idle timeout stored in the index is too old and will be updated.' + } else { + // The timeout after which we update index is two times longer than configured idle timeout + // since index updates are costly and we want to minimize them. + const { idleTimeout } = this.options.config.session.getExpirationTimeouts( + sessionValue.provider ); - updateSessionIndex = true; + if ( + idleTimeout !== null && + idleTimeout.asMilliseconds() * 2 < + sessionExpirationInfo.idleTimeoutExpiration! - + sessionValue.metadata.index.idleTimeoutExpiration! + ) { + // 3. If idle timeout was updated a while ago. + sessionLogger.debug( + 'Session idle timeout stored in the index is too old and will be updated.' + ); + updateSessionIndex = true; + } } // First try to store session in the index and only then in the cookie to make sure cookie is @@ -375,18 +364,21 @@ export class Session { } private calculateExpiry( + provider: AuthenticationProvider, currentLifespanExpiration?: number | null ): { idleTimeoutExpiration: number | null; lifespanExpiration: number | null } { const now = Date.now(); + const { idleTimeout, lifespan } = this.options.config.session.getExpirationTimeouts(provider); + // if we are renewing an existing session, use its `lifespanExpiration` -- otherwise, set this value // based on the configured server `lifespan`. // note, if the server had a `lifespan` set and then removes it, remove `lifespanExpiration` on renewed sessions // also, if the server did not have a `lifespan` set and then adds it, add `lifespanExpiration` on renewed sessions const lifespanExpiration = - currentLifespanExpiration && this.lifespan + currentLifespanExpiration && lifespan ? currentLifespanExpiration - : this.lifespan && now + this.lifespan.asMilliseconds(); - const idleTimeoutExpiration = this.idleTimeout && now + this.idleTimeout.asMilliseconds(); + : lifespan && now + lifespan.asMilliseconds(); + const idleTimeoutExpiration = idleTimeout && now + idleTimeout.asMilliseconds(); return { idleTimeoutExpiration, lifespanExpiration }; } diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index cba63412bf5022..1dd47c7ff66e84 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -194,8 +194,39 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { range: { idleTimeoutExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider that are expired based on the idle timeout. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, + }, ], }, }, @@ -226,9 +257,49 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { bool: { must_not: { exists: { field: 'lifespanExpiration' } } } }, - { range: { idleTimeoutExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a particular provider that are expired based on the idle timeout. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, + }, ], }, }, @@ -260,9 +331,43 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, ], }, }, @@ -294,10 +399,179 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { bool: { must_not: { exists: { field: 'lifespanExpiration' } } } }, - { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a particular provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + }, + }); + }); + + it('when both `lifespan` and `idleTimeout` are configured and multiple providers are enabled', async () => { + const globalIdleTimeout = 123; + const samlIdleTimeout = 33221; + sessionIndex = new SessionIndex({ + logger: loggingSystemMock.createLogger(), + kibanaIndexName: '.kibana_some_tenant', + config: createConfig( + ConfigSchema.validate({ + session: { idleTimeout: globalIdleTimeout, lifespan: 456 }, + authc: { + providers: { + basic: { basic1: { order: 0 } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: samlIdleTimeout }, + }, + }, + }, + }, + }), + loggingSystemMock.createLogger(), + { isTLSEnabled: false } + ), + clusterClient: mockClusterClient, + }); + + await sessionIndex.cleanUp(); + + expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { + index: indexName, + refresh: 'wait_for', + ignore: [409, 404], + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + }, + }, + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a Basic provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a Basic provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * globalIdleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, + // The sessions that belong to a SAML provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a SAML provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * samlIdleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, ], }, }, diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index ee503acc0d3a4e..96fff41d575034 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; -import { AuthenticationProvider } from '../../common/types'; -import { ConfigType } from '../config'; +import type { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; +import type { AuthenticationProvider } from '../../common/types'; +import type { ConfigType } from '../config'; export interface SessionIndexOptions { readonly clusterClient: ILegacyClusterClient; readonly kibanaIndexName: string; - readonly config: Pick; + readonly config: Pick; readonly logger: Logger; } @@ -120,12 +120,6 @@ export class SessionIndex { */ private readonly indexName = `${this.options.kibanaIndexName}_security_session_${SESSION_INDEX_TEMPLATE_VERSION}`; - /** - * Timeout after which session with the expired idle timeout _may_ be removed from the index - * during regular cleanup routine. - */ - private readonly idleIndexCleanupTimeout: number | null; - /** * Promise that tracks session index initialization process. We'll need to get rid of this as soon * as Core provides support for plugin statuses (https://github.com/elastic/kibana/issues/41983). @@ -134,14 +128,7 @@ export class SessionIndex { */ private indexInitialization?: Promise; - constructor(private readonly options: Readonly) { - // This timeout is intentionally larger than the `idleIndexUpdateTimeout` (idleTimeout * 2) - // configured in `Session` to be sure that the session value is definitely expired and may be - // safely cleaned up. - this.idleIndexCleanupTimeout = this.options.config.session.idleTimeout - ? this.options.config.session.idleTimeout.asMilliseconds() * 3 - : null; - } + constructor(private readonly options: Readonly) {} /** * Retrieves session value with the specified ID from the index. If session value isn't found @@ -353,26 +340,62 @@ export class SessionIndex { this.options.logger.debug(`Running cleanup routine.`); const now = Date.now(); + const providersSessionConfig = this.options.config.authc.sortedProviders.map((provider) => { + return { + boolQuery: { + bool: { + must: [ + { term: { 'provider.type': provider.type } }, + { term: { 'provider.name': provider.name } }, + ], + }, + }, + ...this.options.config.session.getExpirationTimeouts(provider), + }; + }); // Always try to delete sessions with expired lifespan (even if it's not configured right now). const deleteQueries: object[] = [{ range: { lifespanExpiration: { lte: now } } }]; - // If lifespan is configured we should remove any sessions that were created without one. - if (this.options.config.session.lifespan) { - deleteQueries.push({ bool: { must_not: { exists: { field: 'lifespanExpiration' } } } }); - } + // If session belongs to a not configured provider we should also remove it. + deleteQueries.push({ + bool: { + must_not: { + bool: { + should: providersSessionConfig.map(({ boolQuery }) => boolQuery), + minimum_should_match: 1, + }, + }, + }, + }); - // If idle timeout is configured we should delete all sessions without specified idle timeout - // or if that session hasn't been updated for a while meaning that session is expired. - if (this.idleIndexCleanupTimeout) { - deleteQueries.push( - { range: { idleTimeoutExpiration: { lte: now - this.idleIndexCleanupTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } } - ); - } else { - // Otherwise just delete all expired sessions that were previously created with the idle - // timeout. - deleteQueries.push({ range: { idleTimeoutExpiration: { lte: now } } }); + for (const { boolQuery, lifespan, idleTimeout } of providersSessionConfig) { + // If lifespan is configured we should remove any sessions that were created without one. + if (lifespan) { + deleteQueries.push({ + bool: { ...boolQuery.bool, must_not: { exists: { field: 'lifespanExpiration' } } }, + }); + } + + // This timeout is intentionally larger than the timeout used in `Session` to update idle + // timeout in the session index (idleTimeout * 2) to be sure that the session value is + // definitely expired and may be safely cleaned up. + const idleIndexCleanupTimeout = idleTimeout ? idleTimeout.asMilliseconds() * 3 : null; + deleteQueries.push({ + bool: { + ...boolQuery.bool, + // If idle timeout is configured we should delete all sessions without specified idle timeout + // or if that session hasn't been updated for a while meaning that session is expired. Otherwise + // just delete all expired sessions that were previously created with the idle timeout. + should: idleIndexCleanupTimeout + ? [ + { range: { idleTimeoutExpiration: { lte: now - idleIndexCleanupTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ] + : [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, + }); } try { diff --git a/x-pack/test/api_integration/apis/security/basic_login.js b/x-pack/test/api_integration/apis/security/basic_login.js index 43ef8e6b81eac4..a3049814b8e52b 100644 --- a/x-pack/test/api_integration/apis/security/basic_login.js +++ b/x-pack/test/api_integration/apis/security/basic_login.js @@ -147,9 +147,9 @@ export default function ({ getService }) { 'authentication_type', ]); expect(apiResponse.body.username).to.be(validUsername); - expect(apiResponse.body.authentication_provider).to.eql('__http__'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'http', name: '__http__' }); expect(apiResponse.body.authentication_type).to.be('realm'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); describe('with session cookie', () => { @@ -193,9 +193,9 @@ export default function ({ getService }) { 'authentication_type', ]); expect(apiResponse.body.username).to.be(validUsername); - expect(apiResponse.body.authentication_provider).to.eql('basic'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(apiResponse.body.authentication_type).to.be('realm'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); it('should extend cookie on every successful non-system API call', async () => { diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 459dc4739897c5..c31f6b689e972c 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -79,9 +79,9 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(user.authentication_type).to.eql('realm'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); describe('initiating SPNEGO', () => { @@ -146,7 +146,7 @@ export default function ({ getService }: FtrProviderContext) { enabled: true, authentication_realm: { name: 'kerb1', type: 'kerberos' }, lookup_realm: { name: 'kerb1', type: 'kerberos' }, - authentication_provider: 'kerberos', + authentication_provider: { type: 'kerberos', name: 'kerberos' }, authentication_type: 'token', }); }); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts index c2335cf04504fa..1fdb15a86ce0a2 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts @@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(user.authentication_type).to.be('realm'); // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud }); @@ -235,7 +235,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be('user1'); expect(apiResponse.body.authentication_realm).to.eql({ name: 'oidc1', type: 'oidc' }); - expect(apiResponse.body.authentication_provider).to.eql('oidc'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'oidc', name: 'oidc' }); expect(apiResponse.body.authentication_type).to.be('token'); }); }); @@ -289,7 +289,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be('user2'); expect(apiResponse.body.authentication_realm).to.eql({ name: 'oidc1', type: 'oidc' }); - expect(apiResponse.body.authentication_provider).to.eql('oidc'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'oidc', name: 'oidc' }); expect(apiResponse.body.authentication_type).to.be('token'); }); }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index e4e194a619a954..7c408d8b903e32 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -156,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be('user1'); expect(apiResponse.body.authentication_realm).to.eql({ name: 'oidc1', type: 'oidc' }); - expect(apiResponse.body.authentication_provider).to.eql('oidc'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'oidc', name: 'oidc' }); expect(apiResponse.body.authentication_type).to.be('token'); }); }); diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts index 0559e9e96fe3f7..43b728d12311d8 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts @@ -93,8 +93,8 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); it('should properly set cookie and authenticate user', async () => { @@ -123,7 +123,7 @@ export default function ({ getService }: FtrProviderContext) { }, authentication_realm: { name: 'pki1', type: 'pki' }, lookup_realm: { name: 'pki1', type: 'pki' }, - authentication_provider: 'pki', + authentication_provider: { name: 'pki', type: 'pki' }, authentication_type: 'token', }); @@ -168,7 +168,7 @@ export default function ({ getService }: FtrProviderContext) { }, authentication_realm: { name: 'pki1', type: 'pki' }, lookup_realm: { name: 'pki1', type: 'pki' }, - authentication_provider: 'pki', + authentication_provider: { name: 'pki', type: 'pki' }, authentication_type: 'token', }); diff --git a/x-pack/test/security_api_integration/session_idle.config.ts b/x-pack/test/security_api_integration/session_idle.config.ts index 34a23b7f5f9263..b8f9141c0a29e2 100644 --- a/x-pack/test/security_api_integration/session_idle.config.ts +++ b/x-pack/test/security_api_integration/session_idle.config.ts @@ -15,16 +15,35 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ); const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); + const idpPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); + return { testFiles: [resolve(__dirname, './tests/session_idle')], services: { + randomness: kibanaAPITestsConfig.get('services.randomness'), legacyEs: kibanaAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, servers: xPackAPITestsConfig.get('servers'), - esTestCluster: xPackAPITestsConfig.get('esTestCluster'), + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.token.timeout=15s', + 'xpack.security.authc.realms.saml.saml1.order=0', + `xpack.security.authc.realms.saml.saml1.idp.metadata.path=${idpPath}`, + 'xpack.security.authc.realms.saml.saml1.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml1.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml1.attributes.principal=urn:oid:0.0.7', + ], + }, kbnTestServer: { ...xPackAPITestsConfig.get('kbnTestServer'), @@ -32,6 +51,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), '--xpack.security.session.idleTimeout=5s', '--xpack.security.session.cleanupInterval=10s', + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 0 } }, + saml: { + saml_fallback: { order: 1, realm: 'saml1' }, + saml_override: { order: 2, realm: 'saml1', session: { idleTimeout: '1m' } }, + saml_disable: { order: 3, realm: 'saml1', session: { idleTimeout: 0 } }, + }, + })}`, ], }, diff --git a/x-pack/test/security_api_integration/session_lifespan.config.ts b/x-pack/test/security_api_integration/session_lifespan.config.ts index b5fdf6b6914b14..4001a963bfae8f 100644 --- a/x-pack/test/security_api_integration/session_lifespan.config.ts +++ b/x-pack/test/security_api_integration/session_lifespan.config.ts @@ -15,16 +15,35 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ); const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); + const idpPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); + return { testFiles: [resolve(__dirname, './tests/session_lifespan')], services: { + randomness: kibanaAPITestsConfig.get('services.randomness'), legacyEs: kibanaAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, servers: xPackAPITestsConfig.get('servers'), - esTestCluster: xPackAPITestsConfig.get('esTestCluster'), + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.token.timeout=15s', + 'xpack.security.authc.realms.saml.saml1.order=0', + `xpack.security.authc.realms.saml.saml1.idp.metadata.path=${idpPath}`, + 'xpack.security.authc.realms.saml.saml1.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml1.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml1.attributes.principal=urn:oid:0.0.7', + ], + }, kbnTestServer: { ...xPackAPITestsConfig.get('kbnTestServer'), @@ -32,6 +51,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), '--xpack.security.session.lifespan=5s', '--xpack.security.session.cleanupInterval=10s', + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 0 } }, + saml: { + saml_fallback: { order: 1, realm: 'saml1' }, + saml_override: { order: 2, realm: 'saml1', session: { lifespan: '1m' } }, + saml_disable: { order: 3, realm: 'saml1', session: { lifespan: 0 } }, + }, + })}`, ], }, diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 2881020f521eed..432fd6ff912806 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -10,6 +10,7 @@ import { resolve } from 'path'; import url from 'url'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; import { getStateAndNonce } from '../../../oidc_api_integration/fixtures/oidc_tools'; import { getMutualAuthenticationResponseToken, @@ -35,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { async function checkSessionCookie( sessionCookie: Cookie, username: string, - providerName: string, + provider: AuthenticationProvider, authenticationRealm: { name: string; type: string } | null, authenticationType: string ) { @@ -66,7 +67,7 @@ export default function ({ getService }: FtrProviderContext) { ]); expect(apiResponse.body.username).to.be(username); - expect(apiResponse.body.authentication_provider).to.be(providerName); + expect(apiResponse.body.authentication_provider).to.eql(provider); if (authenticationRealm) { expect(apiResponse.body.authentication_realm).to.eql(authenticationRealm); } @@ -146,11 +147,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -182,11 +180,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -215,11 +210,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -244,7 +236,13 @@ export default function ({ getService }: FtrProviderContext) { )!; // Skip auth provider check since this comes from the reserved realm, // which is not available when running on ESS - await checkSessionCookie(basicSessionCookie, 'elastic', 'basic1', null, 'realm'); + await checkSessionCookie( + basicSessionCookie, + 'elastic', + { type: 'basic', name: 'basic1' }, + null, + 'realm' + ); const authenticationResponse = await supertest .post('/api/security/saml/callback') @@ -267,11 +265,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -293,11 +288,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml1SessionCookie, 'a@b.c', - 'saml1', - { - name: 'saml1', - type: 'saml', - }, + { type: 'saml', name: 'saml1' }, + { name: 'saml1', type: 'saml' }, 'token' ); @@ -321,11 +313,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml2SessionCookie, 'a@b.c', - 'saml2', - { - name: 'saml2', - type: 'saml', - }, + { type: 'saml', name: 'saml2' }, + { name: 'saml2', type: 'saml' }, 'token' ); }); @@ -346,11 +335,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml1SessionCookie, 'a@b.c', - 'saml1', - { - name: 'saml1', - type: 'saml', - }, + { type: 'saml', name: 'saml1' }, + { name: 'saml1', type: 'saml' }, 'token' ); @@ -376,11 +362,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml2SessionCookie, 'a@b.c', - 'saml2', - { - name: 'saml2', - type: 'saml', - }, + { type: 'saml', name: 'saml2' }, + { name: 'saml2', type: 'saml' }, 'token' ); }); @@ -466,11 +449,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -537,11 +517,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml2SessionCookie, 'a@b.c', - 'saml2', - { - name: 'saml2', - type: 'saml', - }, + { type: 'saml', name: 'saml2' }, + { name: 'saml2', type: 'saml' }, 'token' ); }); @@ -586,11 +563,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'tester@TEST.ELASTIC.CO', - 'kerberos1', - { - name: 'kerb1', - type: 'kerberos', - }, + { type: 'kerberos', name: 'kerberos1' }, + { name: 'kerb1', type: 'kerberos' }, 'token' ); }); @@ -635,11 +609,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'tester@TEST.ELASTIC.CO', - 'kerberos1', - { - name: 'kerb1', - type: 'kerberos', - }, + { type: 'kerberos', name: 'kerberos1' }, + { name: 'kerb1', type: 'kerberos' }, 'token' ); }); @@ -677,11 +648,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'user2', - 'oidc1', - { - name: 'oidc1', - type: 'oidc', - }, + { type: 'oidc', name: 'oidc1' }, + { name: 'oidc1', type: 'oidc' }, 'token' ); }); @@ -737,11 +705,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'user1', - 'oidc1', - { - name: 'oidc1', - type: 'oidc', - }, + { type: 'oidc', name: 'oidc1' }, + { name: 'oidc1', type: 'oidc' }, 'token' ); }); @@ -779,11 +744,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'first_client', - 'pki1', - { - name: 'pki1', - type: 'pki', - }, + { type: 'pki', name: 'pki1' }, + { name: 'pki1', type: 'pki' }, 'token' ); }); diff --git a/x-pack/test/security_api_integration/tests/saml/saml_login.ts b/x-pack/test/security_api_integration/tests/saml/saml_login.ts index 8770d87c0cf8ce..030c6f91d2aedc 100644 --- a/x-pack/test/security_api_integration/tests/saml/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml/saml_login.ts @@ -65,7 +65,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be(username); expect(apiResponse.body.authentication_realm).to.eql({ name: 'saml1', type: 'saml' }); - expect(apiResponse.body.authentication_provider).to.eql('saml'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'saml', name: 'saml' }); expect(apiResponse.body.authentication_type).to.be('token'); } @@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(user.authentication_type).to.be('realm'); // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud }); diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index 703180442f8f5d..c1e8bb9938986a 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -7,6 +7,8 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -14,9 +16,15 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const config = getService('config'); const log = getService('log'); - const [username, password] = config.get('servers.elasticsearch.auth').split(':'); - - async function checkSessionCookie(sessionCookie: Cookie, providerName: string) { + const randomness = getService('randomness'); + const [basicUsername, basicPassword] = config.get('servers.elasticsearch.auth').split(':'); + const kibanaServerConfig = config.get('servers.kibana'); + + async function checkSessionCookie( + sessionCookie: Cookie, + username: string, + provider: AuthenticationProvider + ) { const apiResponse = await supertest .get('/internal/security/me') .set('kbn-xsrf', 'xxx') @@ -24,9 +32,11 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(apiResponse.body.username).to.be(username); - expect(apiResponse.body.authentication_provider).to.be(providerName); + expect(apiResponse.body.authentication_provider).to.eql(provider); - return request.cookie(apiResponse.headers['set-cookie'][0])!; + return Array.isArray(apiResponse.headers['set-cookie']) + ? request.cookie(apiResponse.headers['set-cookie'][0])! + : undefined; } async function getNumberOfSessionDocuments() { @@ -35,6 +45,31 @@ export default function ({ getService }: FtrProviderContext) { }).value; } + async function loginWithSAML(providerName: string) { + const handshakeResponse = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ providerType: 'saml', providerName, currentURL: '' }) + .expect(200); + + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .set('kbn-xsrf', 'xxx') + .set('Cookie', request.cookie(handshakeResponse.headers['set-cookie'][0])!.cookieString()) + .send({ + SAMLResponse: await getSAMLResponse({ + destination: `http://localhost:${kibanaServerConfig.port}/api/security/saml/callback`, + sessionIndex: String(randomness.naturalNumber()), + inResponseTo: await getSAMLRequestId(handshakeResponse.body.location), + }), + }) + .expect(302); + + const cookie = request.cookie(authenticationResponse.headers['set-cookie'][0])!; + await checkSessionCookie(cookie, 'a@b.c', { type: 'saml', name: providerName }); + return cookie; + } + describe('Session Idle cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', waitForStatus: 'green' }); @@ -52,14 +87,14 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', - params: { username, password }, + params: { username: basicUsername, password: basicPassword }, }) .expect(200); const sessionCookie = request.cookie(response.headers['set-cookie'][0])!; - await checkSessionCookie(sessionCookie, 'basic'); + await checkSessionCookie(sessionCookie, basicUsername, { type: 'basic', name: 'basic1' }); expect(await getNumberOfSessionDocuments()).to.be(1); // Cleanup routine runs every 10s, and idle timeout threshold is three times larger than 5s @@ -76,6 +111,66 @@ export default function ({ getService }: FtrProviderContext) { .expect(401); }); + it('should properly clean up session expired because of idle timeout when providers override global session config', async function () { + this.timeout(60000); + + const [ + samlDisableSessionCookie, + samlOverrideSessionCookie, + samlFallbackSessionCookie, + ] = await Promise.all([ + loginWithSAML('saml_disable'), + loginWithSAML('saml_override'), + loginWithSAML('saml_fallback'), + ]); + + const response = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic1', + currentURL: '/', + params: { username: basicUsername, password: basicPassword }, + }) + .expect(200); + + const basicSessionCookie = request.cookie(response.headers['set-cookie'][0])!; + await checkSessionCookie(basicSessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }); + expect(await getNumberOfSessionDocuments()).to.be(4); + + // Cleanup routine runs every 10s, and idle timeout threshold is three times larger than 5s + // idle timeout, let's wait for 30s to make sure cleanup routine runs when idle timeout + // threshold is exceeded. + await delay(30000); + + // Session for basic and SAML that used global session settings should not be valid anymore. + expect(await getNumberOfSessionDocuments()).to.be(2); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlFallbackSessionCookie.cookieString()) + .expect(401); + + // But sessions for the SAML with overridden and disabled lifespan should still be valid. + await checkSessionCookie(samlOverrideSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_override', + }); + await checkSessionCookie(samlDisableSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_disable', + }); + }); + it('should not clean up session if user is active', async function () { this.timeout(60000); @@ -84,14 +179,14 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', - params: { username, password }, + params: { username: basicUsername, password: basicPassword }, }) .expect(200); let sessionCookie = request.cookie(response.headers['set-cookie'][0])!; - await checkSessionCookie(sessionCookie, 'basic'); + await checkSessionCookie(sessionCookie, basicUsername, { type: 'basic', name: 'basic1' }); expect(await getNumberOfSessionDocuments()).to.be(1); // Run 20 consequent requests with 1.5s delay, during this time cleanup procedure should run at @@ -100,7 +195,10 @@ export default function ({ getService }: FtrProviderContext) { // Session idle timeout is 15s, let's wait 10s and make a new request that would extend the session. await delay(1500); - sessionCookie = await checkSessionCookie(sessionCookie, 'basic'); + sessionCookie = (await checkSessionCookie(sessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }))!; log.debug(`Session is still valid after ${(counter + 1) * 1.5}s`); } diff --git a/x-pack/test/security_api_integration/tests/session_idle/extension.ts b/x-pack/test/security_api_integration/tests/session_idle/extension.ts index 64ecdda2013010..16945184c0f6f5 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/extension.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/extension.ts @@ -47,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', params: { username: validUsername, password: validPassword }, }) @@ -61,7 +61,7 @@ export default function ({ getService }: FtrProviderContext) { expect(body.now).to.be.a('number'); expect(body.idleTimeoutExpiration).to.be.a('number'); expect(body.lifespanExpiration).to.be(null); - expect(body.provider).to.eql({ type: 'basic', name: 'basic' }); + expect(body.provider).to.eql({ type: 'basic', name: 'basic1' }); }); it('should not extend the session', async () => { diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 8b136e540f13fb..59e8c746a6d077 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -7,15 +7,23 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const es = getService('legacyEs'); const config = getService('config'); - const [username, password] = config.get('servers.elasticsearch.auth').split(':'); + const randomness = getService('randomness'); + const [basicUsername, basicPassword] = config.get('servers.elasticsearch.auth').split(':'); + const kibanaServerConfig = config.get('servers.kibana'); - async function checkSessionCookie(sessionCookie: Cookie, providerName: string) { + async function checkSessionCookie( + sessionCookie: Cookie, + username: string, + provider: AuthenticationProvider + ) { const apiResponse = await supertest .get('/internal/security/me') .set('kbn-xsrf', 'xxx') @@ -23,7 +31,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(apiResponse.body.username).to.be(username); - expect(apiResponse.body.authentication_provider).to.be(providerName); + expect(apiResponse.body.authentication_provider).to.eql(provider); } async function getNumberOfSessionDocuments() { @@ -32,6 +40,31 @@ export default function ({ getService }: FtrProviderContext) { }).value; } + async function loginWithSAML(providerName: string) { + const handshakeResponse = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ providerType: 'saml', providerName, currentURL: '' }) + .expect(200); + + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .set('kbn-xsrf', 'xxx') + .set('Cookie', request.cookie(handshakeResponse.headers['set-cookie'][0])!.cookieString()) + .send({ + SAMLResponse: await getSAMLResponse({ + destination: `http://localhost:${kibanaServerConfig.port}/api/security/saml/callback`, + sessionIndex: String(randomness.naturalNumber()), + inResponseTo: await getSAMLRequestId(handshakeResponse.body.location), + }), + }) + .expect(302); + + const cookie = request.cookie(authenticationResponse.headers['set-cookie'][0])!; + await checkSessionCookie(cookie, 'a@b.c', { type: 'saml', name: providerName }); + return cookie; + } + describe('Session Lifespan cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', waitForStatus: 'green' }); @@ -49,14 +82,17 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', - params: { username, password }, + params: { username: basicUsername, password: basicPassword }, }) .expect(200); const sessionCookie = request.cookie(response.headers['set-cookie'][0])!; - await checkSessionCookie(sessionCookie, 'basic'); + await checkSessionCookie(sessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }); expect(await getNumberOfSessionDocuments()).to.be(1); // Cleanup routine runs every 10s, let's wait for 30s to make sure it runs multiple times and @@ -71,5 +107,63 @@ export default function ({ getService }: FtrProviderContext) { .set('Cookie', sessionCookie.cookieString()) .expect(401); }); + + it('should properly clean up session expired because of lifespan when providers override global session config', async function () { + this.timeout(60000); + + const [ + samlDisableSessionCookie, + samlOverrideSessionCookie, + samlFallbackSessionCookie, + ] = await Promise.all([ + loginWithSAML('saml_disable'), + loginWithSAML('saml_override'), + loginWithSAML('saml_fallback'), + ]); + + const response = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic1', + currentURL: '/', + params: { username: basicUsername, password: basicPassword }, + }) + .expect(200); + const basicSessionCookie = request.cookie(response.headers['set-cookie'][0])!; + await checkSessionCookie(basicSessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }); + expect(await getNumberOfSessionDocuments()).to.be(4); + + // Cleanup routine runs every 10s, let's wait for 30s to make sure it runs multiple times and + // when lifespan is exceeded. + await delay(30000); + + // Session for basic and SAML that used global session settings should not be valid anymore. + expect(await getNumberOfSessionDocuments()).to.be(2); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlFallbackSessionCookie.cookieString()) + .expect(401); + + // But sessions for the SAML with overridden and disabled lifespan should still be valid. + await checkSessionCookie(samlOverrideSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_override', + }); + await checkSessionCookie(samlDisableSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_disable', + }); + }); }); } From 82a83022435073576c45276e1dadc699e122761f Mon Sep 17 00:00:00 2001 From: John Schulz Date: Fri, 6 Nov 2020 11:26:12 -0500 Subject: [PATCH 33/81] [Ingest Manager] Remove dupe code between two unpack*ToCache fns (#82801) ## Summary Update `unpackRegistryPackageToCache` to call `unpackArchiveToCache` instead of duplicating much of it. Now an archive is iterated & put in cache via the same function regardless of its initial source. --- package.json | 3 ++- .../server/services/epm/archive/index.ts | 6 ++--- .../server/services/epm/registry/index.ts | 24 ++++++------------- .../apis/epm/install_by_upload.ts | 4 ++-- yarn.lock | 2 +- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 322bdf445758ac..0fce2594f63868 100644 --- a/package.json +++ b/package.json @@ -237,6 +237,7 @@ "markdown-it": "^10.0.0", "md5": "^2.1.0", "mime": "^2.4.4", + "mime-types": "^2.1.27", "mini-css-extract-plugin": "0.8.0", "minimatch": "^3.0.4", "moment": "^2.24.0", @@ -854,4 +855,4 @@ "yo": "2.0.6", "zlib": "^1.0.5" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts index 395f9c15b3b878..fe5a395804932c 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts @@ -53,7 +53,7 @@ export async function unpackArchiveToCache( await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { const { path, buffer } = entry; // skip directories - if (path.slice(-1) === '/') return; + if (path.endsWith('/')) return; if (buffer) { cacheSet(path, buffer); paths.push(path); @@ -61,7 +61,7 @@ export async function unpackArchiveToCache( }); } catch (error) { throw new PackageInvalidArchiveError( - `Error during extraction of uploaded package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.` + `Error during extraction of package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.` ); } @@ -69,7 +69,7 @@ export async function unpackArchiveToCache( // unpacking a zip file with untarBuffer() just results in nothing. if (paths.length === 0) { throw new PackageInvalidArchiveError( - `Uploaded archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.` + `Archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.` ); } return paths; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index e6d14a7846c225..b1dd9a8c3c3f19 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import mime from 'mime-types'; import semver from 'semver'; import { Response } from 'node-fetch'; import { URL } from 'url'; @@ -18,15 +18,15 @@ import { RegistrySearchResults, RegistrySearchResult, } from '../../../types'; +import { unpackArchiveToCache } from '../archive'; import { cacheGet, - cacheSet, cacheDelete, getArchiveFilelist, setArchiveFilelist, deleteArchiveFilelist, } from './cache'; -import { ArchiveEntry, getBufferExtractor } from './extract'; +import { ArchiveEntry } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; import { getRegistryUrl } from './registry_url'; @@ -137,22 +137,12 @@ export async function unpackRegistryPackageToCache( pkgVersion: string, filter = (entry: ArchiveEntry): boolean => true ): Promise { - const paths: string[] = []; const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion); - const bufferExtractor = getBufferExtractor({ archivePath }); - if (!bufferExtractor) { - throw new Error('Unknown compression format. Please use .zip or .gz'); + const contentType = mime.lookup(archivePath); + if (!contentType) { + throw new Error(`Unknown compression format for '${archivePath}'. Please use .zip or .gz`); } - await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { - const { path, buffer } = entry; - const { file } = pathParts(path); - if (!file) return; - if (buffer) { - cacheSet(path, buffer); - paths.push(path); - } - }); - + const paths: string[] = await unpackArchiveToCache(archiveBuffer, contentType); return paths; } diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts index 4ad9501f3936f6..a5f1aa8003f044 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts @@ -92,7 +92,7 @@ export default function (providerContext: FtrProviderContext) { .send(buf) .expect(400); expect(res.error.text).to.equal( - '{"statusCode":400,"error":"Bad Request","message":"Uploaded archive seems empty. Assumed content type was application/gzip, check if this matches the archive type."}' + '{"statusCode":400,"error":"Bad Request","message":"Archive seems empty. Assumed content type was application/gzip, check if this matches the archive type."}' ); }); @@ -105,7 +105,7 @@ export default function (providerContext: FtrProviderContext) { .send(buf) .expect(400); expect(res.error.text).to.equal( - '{"statusCode":400,"error":"Bad Request","message":"Error during extraction of uploaded package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."}' + '{"statusCode":400,"error":"Bad Request","message":"Error during extraction of package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."}' ); }); diff --git a/yarn.lock b/yarn.lock index 6e6647016dac2c..c3d36f42d31807 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20577,7 +20577,7 @@ mime-db@1.44.0, mime-db@1.x.x, "mime-db@>= 1.40.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== From 0e8985c3fb49bb8dbb068204125f8af3156e7a13 Mon Sep 17 00:00:00 2001 From: Poff Poffenberger Date: Fri, 6 Nov 2020 10:41:35 -0600 Subject: [PATCH 34/81] [Canvas] Fix elements not being updated properly when filter is changed on workpad (#81863) * Update renderer handlers when element is changed * Update handlers before render * Add canvas functional test for filters * Update snapshot and remove log Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../dropdown_filter.stories.storyshot | 5 + .../component/dropdown_filter.tsx | 7 +- .../__snapshots__/debug.stories.storyshot | 2 + .../canvas/public/components/debug/debug.tsx | 4 +- .../element_content/element_content.js | 20 +- .../render_with_fn/render_with_fn.tsx | 8 +- x-pack/test/functional/apps/canvas/filters.ts | 89 + x-pack/test/functional/apps/canvas/index.js | 1 + .../es_archives/canvas/filter/data.json.gz | Bin 0 -> 958 bytes .../es_archives/canvas/filter/mappings.json | 2422 +++++++++++++++++ .../functional/page_objects/canvas_page.ts | 22 + 11 files changed, 2559 insertions(+), 21 deletions(-) create mode 100644 x-pack/test/functional/apps/canvas/filters.ts create mode 100644 x-pack/test/functional/es_archives/canvas/filter/data.json.gz create mode 100644 x-pack/test/functional/es_archives/canvas/filter/mappings.json diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot index 5ad49a207ed3ec..286c55994f27ec 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot @@ -6,6 +6,7 @@ exports[`Storyshots renderers/DropdownFilter default 1`] = ` >
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx index c5fe7074fea0bf..07d749c5677dcf 100644 --- a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx +++ b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx @@ -6,6 +6,8 @@ import React, { useState, useEffect, useRef, FC, useCallback } from 'react'; +import { isEqual } from 'lodash'; + import { useNotifyService } from '../../services'; import { RenderToDom } from '../render_to_dom'; import { ErrorStrings } from '../../../i18n'; @@ -82,8 +84,12 @@ export const RenderWithFn: FC = ({ ); const render = useCallback(() => { + if (!isEqual(handlers.current, incomingHandlers)) { + handlers.current = incomingHandlers; + } + renderFn(renderTarget.current!, config, handlers.current); - }, [renderTarget, config, renderFn]); + }, [renderTarget, config, renderFn, incomingHandlers]); useEffect(() => { if (!domNode) { diff --git a/x-pack/test/functional/apps/canvas/filters.ts b/x-pack/test/functional/apps/canvas/filters.ts new file mode 100644 index 00000000000000..a4bd806597a933 --- /dev/null +++ b/x-pack/test/functional/apps/canvas/filters.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function canvasFiltersTest({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['canvas', 'common']); + const find = getService('find'); + const esArchiver = getService('esArchiver'); + + describe('filters', function () { + // there is an issue with FF not properly clicking on workpad elements + this.tags('skipFirefox'); + + before(async () => { + await esArchiver.load('canvas/filter'); + // load test workpad + await PageObjects.common.navigateToApp('canvas', { + hash: '/workpad/workpad-b5618217-56d2-47fa-b756-1be2306cda68/page/1', + }); + }); + + it('filter updates when dropdown is changed', async () => { + // wait for all our elements to load up + await retry.try(async () => { + const elements = await testSubjects.findAll( + 'canvasWorkpadPage > canvasWorkpadPageElementContent' + ); + expect(elements).to.have.length(3); + }); + + // Double check that the filter has the correct time range and default filter value + const startingMatchFilters = await PageObjects.canvas.getMatchFiltersFromDebug(); + expect(startingMatchFilters[0].value).to.equal('apm'); + expect(startingMatchFilters[0].column).to.equal('project'); + + // Change dropdown value + await testSubjects.selectValue('canvasDropdownFilter__select', 'beats'); + + await retry.try(async () => { + const matchFilters = await PageObjects.canvas.getMatchFiltersFromDebug(); + expect(matchFilters[0].value).to.equal('beats'); + expect(matchFilters[0].column).to.equal('project'); + }); + }); + + it('filter updates when time range is changed', async () => { + // wait for all our elements to load up + await retry.try(async () => { + const elements = await testSubjects.findAll( + 'canvasWorkpadPage > canvasWorkpadPageElementContent' + ); + expect(elements).to.have.length(3); + }); + + const startingTimeFilters = await PageObjects.canvas.getTimeFiltersFromDebug(); + expect(startingTimeFilters[0].column).to.equal('@timestamp'); + expect(new Date(startingTimeFilters[0].from).toDateString()).to.equal('Sun Oct 18 2020'); + expect(new Date(startingTimeFilters[0].to).toDateString()).to.equal('Sat Oct 24 2020'); + + await testSubjects.click('superDatePickerstartDatePopoverButton'); + await find.clickByCssSelector('.react-datepicker [aria-label="day-19"]', 20000); + + await retry.try(async () => { + const timeFilters = await PageObjects.canvas.getTimeFiltersFromDebug(); + expect(timeFilters[0].column).to.equal('@timestamp'); + expect(new Date(timeFilters[0].from).toDateString()).to.equal('Mon Oct 19 2020'); + expect(new Date(timeFilters[0].to).toDateString()).to.equal('Sat Oct 24 2020'); + }); + + await testSubjects.click('superDatePickerendDatePopoverButton'); + await find.clickByCssSelector('.react-datepicker [aria-label="day-23"]', 20000); + + await retry.try(async () => { + const timeFilters = await PageObjects.canvas.getTimeFiltersFromDebug(); + expect(timeFilters[0].column).to.equal('@timestamp'); + expect(new Date(timeFilters[0].from).toDateString()).to.equal('Mon Oct 19 2020'); + expect(new Date(timeFilters[0].to).toDateString()).to.equal('Fri Oct 23 2020'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/canvas/index.js b/x-pack/test/functional/apps/canvas/index.js index 7ee48beaabb2ae..36fbd812959249 100644 --- a/x-pack/test/functional/apps/canvas/index.js +++ b/x-pack/test/functional/apps/canvas/index.js @@ -22,6 +22,7 @@ export default function canvasApp({ loadTestFile, getService }) { this.tags('ciGroup2'); // CI requires tags ヽ(゜Q。)ノ? loadTestFile(require.resolve('./smoke_test')); loadTestFile(require.resolve('./expression')); + loadTestFile(require.resolve('./filters')); loadTestFile(require.resolve('./custom_elements')); loadTestFile(require.resolve('./feature_controls/canvas_security')); loadTestFile(require.resolve('./feature_controls/canvas_spaces')); diff --git a/x-pack/test/functional/es_archives/canvas/filter/data.json.gz b/x-pack/test/functional/es_archives/canvas/filter/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..524a5c38fc376973952f8047e5380de3362926e5 GIT binary patch literal 958 zcmV;v13~;BiwFP!000026V+B*YveW*e$THk?n?{NV9Ayx2L?*F>~kMV7t&dlql+hQ ze8bk{LOTDwl0D zp`27g{Qy0BY`Xgv)XnKX5;Vn}V1{U_2ufHENHCfrEHsIuR4PdM!0L!+ymo0%i_!f zG2>K3J}1yTrXHA3p;nqX5i*WFP_smE;cw4Hp7?%Z4XKhor($X1b21{e^ue4;N&SFm z$r$*C7|5I@zDrU_ovf^9d=)ytN-O4Ta-}l73J4_951l<^4#WewEoiM))&*p~^=MeW z^TYCwIV=zSWmq2geppMe+Y9)SPRo-D*Z7?- z_IJkWF%r$-qW{npMuR9_Kbr2KXTln)VN|>?N~62rS1k_v;KQj$XL_wQY4sux@>}L* z_S>rHQdhb*zSdfE)P8C^-S173p2)L9XB`K>V4^@XC|HMf?k%FI(JfX zS=xe)s*~4;vb?Ban)dDOkA*UKgY9S~K6=RU`df=+Ol1F>`vCf_az*^*WGI?)7H1jC zRjg5xkr-u!M@ZNNamXp-3jdqQAnE6_L234V>A?R%I+U&&1qNI`RM)hsd92UfJG?>O zjXl$L&0kuY;N0H}@ax)GKiN@GK997viJ`4*zn*$>7oNy=;g{p0q?7gA$L~>B7~)ast9EwRw#jt zqD)hU?8`((No1qVlo#)?H_xTM5a>{Vymv0t1HZdu!>iBKaA-QH-$>j1oX3l|@B?&( zyGEDCook%qiX(mw?BqdM75fgH=|Akq@;z9;k3+VJHc_~u>2$7b_T|Ej$%*Ol@9|CY gL#y0V`xBTq-_A6?9nHEh9IuZ50KK2SU{wqN09G&P^#A|> literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/canvas/filter/mappings.json b/x-pack/test/functional/es_archives/canvas/filter/mappings.json new file mode 100644 index 00000000000000..1f7e4092d85751 --- /dev/null +++ b/x-pack/test/functional/es_archives/canvas/filter/mappings.json @@ -0,0 +1,2422 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "eaf6f5841dbf4cb5e3045860f75f53ca", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "477f214ff61acc3af26a7b7818e380c1", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "2b83397e3eaaaa8ef15e38813f3721c3", + "exception-list": "67f055ab8c10abd7b2ebfd969b836788", + "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", + "ingest-package-policies": "f74dfe498e1849267cda41580b2be110", + "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "43012c7ebc4cb57054e0a490e4b43023", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "executionStatus": { + "properties": { + "error": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } + } + }, + "lastExecutionDate": { + "type": "date" + }, + "status": { + "type": "keyword" + } + } + }, + "meta": { + "properties": { + "versionApiKeyLastmodified": { + "type": "keyword" + } + } + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enterprise_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "install_source": { + "type": "keyword" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "ack_data": { + "type": "text" + }, + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "policy_id": { + "type": "keyword" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "upgrade_started_at": { + "type": "date" + }, + "upgraded_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "dynamic": "false", + "type": "object" + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_policies": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "config_yaml": { + "type": "text" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_urls": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "dynamic": "false", + "type": "object" + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "dynamic": "false", + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "canvas-workpad": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "indexNames": { + "type": "text" + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index 23b5057573b3b7..dcb22a9518936c 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -79,5 +79,27 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.missingOrFail('add-element-button'); }, + + async getTimeFiltersFromDebug() { + await testSubjects.existOrFail('canvasDebug__content'); + + const contentElem = await testSubjects.find('canvasDebug__content'); + const content = await contentElem.getVisibleText(); + + const filters = JSON.parse(content); + + return filters.and.filter((f: any) => f.filterType === 'time'); + }, + + async getMatchFiltersFromDebug() { + await testSubjects.existOrFail('canvasDebug__content'); + + const contentElem = await testSubjects.find('canvasDebug__content'); + const content = await contentElem.getVisibleText(); + + const filters = JSON.parse(content); + + return filters.and.filter((f: any) => f.filterType === 'exactly'); + }, }; } From 6b60599cbae7220ff53b81ea175e01c4d2170d05 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 6 Nov 2020 11:49:53 -0500 Subject: [PATCH 35/81] Remove yeoman & yo (#82825) --- package.json | 2 - packages/kbn-pm/dist/index.js | 2058 ++++++++--------- .../generator-kui/app/component.js | 52 - .../generator-kui/app/documentation.js | 56 - .../generator-kui/component/index.js | 156 -- .../component/templates/_component.scss | 3 - .../component/templates/_index.scss | 1 - .../component/templates/component.js | 35 - .../component/templates/index.js | 3 - .../component/templates/stateless_function.js | 25 - .../generator-kui/component/templates/test.js | 16 - .../generator-kui/documentation/index.js | 238 -- .../templates/documentation_page.js | 41 - .../templates/documentation_page_demo.js | 11 - .../templates/documentation_sandbox.html | 1 - .../templates/documentation_sandbox.js | 27 - .../kbn-ui-framework/generator-kui/utils.js | 56 - packages/kbn-ui-framework/package.json | 4 +- yarn.lock | 1343 +---------- 19 files changed, 1027 insertions(+), 3101 deletions(-) delete mode 100644 packages/kbn-ui-framework/generator-kui/app/component.js delete mode 100644 packages/kbn-ui-framework/generator-kui/app/documentation.js delete mode 100644 packages/kbn-ui-framework/generator-kui/component/index.js delete mode 100644 packages/kbn-ui-framework/generator-kui/component/templates/_component.scss delete mode 100644 packages/kbn-ui-framework/generator-kui/component/templates/_index.scss delete mode 100644 packages/kbn-ui-framework/generator-kui/component/templates/component.js delete mode 100644 packages/kbn-ui-framework/generator-kui/component/templates/index.js delete mode 100644 packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js delete mode 100644 packages/kbn-ui-framework/generator-kui/component/templates/test.js delete mode 100644 packages/kbn-ui-framework/generator-kui/documentation/index.js delete mode 100644 packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js delete mode 100644 packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js delete mode 100644 packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html delete mode 100644 packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js delete mode 100644 packages/kbn-ui-framework/generator-kui/utils.js diff --git a/package.json b/package.json index 0fce2594f63868..821975d11c6388 100644 --- a/package.json +++ b/package.json @@ -851,8 +851,6 @@ "xml-crypto": "^2.0.0", "xmlbuilder": "13.0.2", "yargs": "^15.4.1", - "yeoman-generator": "1.1.1", - "yo": "2.0.6", "zlib": "^1.0.5" } } diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index a780ec96dd9564..b7d9803059aa89 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(506); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(505); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); @@ -106,7 +106,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(504); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -150,7 +150,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(499); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(498); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -8897,9 +8897,9 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(365); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(398); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(399); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(366); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(397); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(398); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8942,10 +8942,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(357); -/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(362); -/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(359); -/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(363); +/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(358); +/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(363); +/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(360); +/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(364); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -32193,7 +32193,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(314); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(349); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(350); /* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(246); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } @@ -32289,13 +32289,13 @@ const childProcess = __webpack_require__(315); const crossSpawn = __webpack_require__(316); const stripFinalNewline = __webpack_require__(329); const npmRunPath = __webpack_require__(330); -const onetime = __webpack_require__(331); -const makeError = __webpack_require__(333); -const normalizeStdio = __webpack_require__(338); -const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(339); -const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(340); -const {mergePromise, getSpawnedPromise} = __webpack_require__(347); -const {joinCommand, parseCommand} = __webpack_require__(348); +const onetime = __webpack_require__(332); +const makeError = __webpack_require__(334); +const normalizeStdio = __webpack_require__(339); +const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(340); +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(341); +const {mergePromise, getSpawnedPromise} = __webpack_require__(348); +const {joinCommand, parseCommand} = __webpack_require__(349); const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; @@ -33274,7 +33274,7 @@ module.exports = input => { "use strict"; const path = __webpack_require__(4); -const pathKey = __webpack_require__(323); +const pathKey = __webpack_require__(331); const npmRunPath = options => { options = { @@ -33327,7 +33327,30 @@ module.exports.env = options => { "use strict"; -const mimicFn = __webpack_require__(332); + +const pathKey = (options = {}) => { + const environment = options.env || process.env; + const platform = options.platform || process.platform; + + if (platform !== 'win32') { + return 'PATH'; + } + + return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path'; +}; + +module.exports = pathKey; +// TODO: Remove this for the next major release +module.exports.default = pathKey; + + +/***/ }), +/* 332 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const mimicFn = __webpack_require__(333); const calledFunctions = new WeakMap(); @@ -33379,7 +33402,7 @@ module.exports.callCount = fn => { /***/ }), -/* 332 */ +/* 333 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33399,12 +33422,12 @@ module.exports.default = mimicFn; /***/ }), -/* 333 */ +/* 334 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {signalsByName} = __webpack_require__(334); +const {signalsByName} = __webpack_require__(335); const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { if (timedOut) { @@ -33492,14 +33515,14 @@ module.exports = makeError; /***/ }), -/* 334 */ +/* 335 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(121); -var _signals=__webpack_require__(335); -var _realtime=__webpack_require__(337); +var _signals=__webpack_require__(336); +var _realtime=__webpack_require__(338); @@ -33569,14 +33592,14 @@ const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumb //# sourceMappingURL=main.js.map /***/ }), -/* 335 */ +/* 336 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(121); -var _core=__webpack_require__(336); -var _realtime=__webpack_require__(337); +var _core=__webpack_require__(337); +var _realtime=__webpack_require__(338); @@ -33610,7 +33633,7 @@ return{name,number,description,supported,action,forced,standard}; //# sourceMappingURL=signals.js.map /***/ }), -/* 336 */ +/* 337 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33889,7 +33912,7 @@ standard:"other"}];exports.SIGNALS=SIGNALS; //# sourceMappingURL=core.js.map /***/ }), -/* 337 */ +/* 338 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33914,7 +33937,7 @@ const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; //# sourceMappingURL=realtime.js.map /***/ }), -/* 338 */ +/* 339 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33973,7 +33996,7 @@ module.exports.node = opts => { /***/ }), -/* 339 */ +/* 340 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34092,14 +34115,14 @@ module.exports = { /***/ }), -/* 340 */ +/* 341 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const isStream = __webpack_require__(341); -const getStream = __webpack_require__(342); -const mergeStream = __webpack_require__(346); +const isStream = __webpack_require__(342); +const getStream = __webpack_require__(343); +const mergeStream = __webpack_require__(347); // `input` option const handleInput = (spawned, input) => { @@ -34196,7 +34219,7 @@ module.exports = { /***/ }), -/* 341 */ +/* 342 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34232,13 +34255,13 @@ module.exports = isStream; /***/ }), -/* 342 */ +/* 343 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pump = __webpack_require__(343); -const bufferStream = __webpack_require__(345); +const pump = __webpack_require__(344); +const bufferStream = __webpack_require__(346); class MaxBufferError extends Error { constructor() { @@ -34297,11 +34320,11 @@ module.exports.MaxBufferError = MaxBufferError; /***/ }), -/* 343 */ +/* 344 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(162) -var eos = __webpack_require__(344) +var eos = __webpack_require__(345) var fs = __webpack_require__(134) // we only need fs to get the ReadStream and WriteStream prototypes var noop = function () {} @@ -34385,7 +34408,7 @@ module.exports = pump /***/ }), -/* 344 */ +/* 345 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(162); @@ -34485,7 +34508,7 @@ module.exports = eos; /***/ }), -/* 345 */ +/* 346 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34544,7 +34567,7 @@ module.exports = options => { /***/ }), -/* 346 */ +/* 347 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34592,7 +34615,7 @@ module.exports = function (/*streams...*/) { /***/ }), -/* 347 */ +/* 348 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34645,7 +34668,7 @@ module.exports = { /***/ }), -/* 348 */ +/* 349 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34690,7 +34713,7 @@ module.exports = { /***/ }), -/* 349 */ +/* 350 */ /***/ (function(module, exports, __webpack_require__) { // Copyright IBM Corp. 2014,2018. All Rights Reserved. @@ -34698,12 +34721,12 @@ module.exports = { // This file is licensed under the Apache License 2.0. // License text available at https://opensource.org/licenses/Apache-2.0 -module.exports = __webpack_require__(350); -module.exports.cli = __webpack_require__(354); +module.exports = __webpack_require__(351); +module.exports.cli = __webpack_require__(355); /***/ }), -/* 350 */ +/* 351 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34718,9 +34741,9 @@ var stream = __webpack_require__(138); var util = __webpack_require__(112); var fs = __webpack_require__(134); -var through = __webpack_require__(351); -var duplexer = __webpack_require__(352); -var StringDecoder = __webpack_require__(353).StringDecoder; +var through = __webpack_require__(352); +var duplexer = __webpack_require__(353); +var StringDecoder = __webpack_require__(354).StringDecoder; module.exports = Logger; @@ -34909,7 +34932,7 @@ function lineMerger(host) { /***/ }), -/* 351 */ +/* 352 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -35023,7 +35046,7 @@ function through (write, end, opts) { /***/ }), -/* 352 */ +/* 353 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -35116,13 +35139,13 @@ function duplex(writer, reader) { /***/ }), -/* 353 */ +/* 354 */ /***/ (function(module, exports) { module.exports = require("string_decoder"); /***/ }), -/* 354 */ +/* 355 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -35133,11 +35156,11 @@ module.exports = require("string_decoder"); -var minimist = __webpack_require__(355); +var minimist = __webpack_require__(356); var path = __webpack_require__(4); -var Logger = __webpack_require__(350); -var pkg = __webpack_require__(356); +var Logger = __webpack_require__(351); +var pkg = __webpack_require__(357); module.exports = cli; @@ -35191,7 +35214,7 @@ function usage($0, p) { /***/ }), -/* 355 */ +/* 356 */ /***/ (function(module, exports) { module.exports = function (args, opts) { @@ -35442,13 +35465,13 @@ function isNumber (x) { /***/ }), -/* 356 */ +/* 357 */ /***/ (function(module) { module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); /***/ }), -/* 357 */ +/* 358 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -35456,13 +35479,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getAllChecksums", function() { return getAllChecksums; }); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(134); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(358); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(359); /* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(112); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(314); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(359); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(360); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -35661,20 +35684,20 @@ async function getAllChecksums(kbn, log, yarnLock) { } /***/ }), -/* 358 */ +/* 359 */ /***/ (function(module, exports) { module.exports = require("crypto"); /***/ }), -/* 359 */ +/* 360 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readYarnLock", function() { return readYarnLock; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resolveDepsForProject", function() { return resolveDepsForProject; }); -/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(360); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(361); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(131); /* @@ -35787,7 +35810,7 @@ function resolveDepsForProject({ } /***/ }), -/* 360 */ +/* 361 */ /***/ (function(module, exports, __webpack_require__) { module.exports = @@ -37346,7 +37369,7 @@ module.exports = invariant; /* 9 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(358); +module.exports = __webpack_require__(359); /***/ }), /* 10 */, @@ -39670,7 +39693,7 @@ function onceStrict (fn) { /* 63 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(361); +module.exports = __webpack_require__(362); /***/ }), /* 64 */, @@ -46065,13 +46088,13 @@ module.exports = process && support(supportLevel); /******/ ]); /***/ }), -/* 361 */ +/* 362 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 362 */ +/* 363 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -46168,13 +46191,13 @@ class BootstrapCacheFile { } /***/ }), -/* 363 */ +/* 364 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validateDependencies", function() { return validateDependencies; }); -/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(360); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(361); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_1__); @@ -46185,7 +46208,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); -/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(364); +/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(365); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -46377,7 +46400,7 @@ function getDevOnlyProductionDepsTree(kbn, projectName) { } /***/ }), -/* 364 */ +/* 365 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -46530,7 +46553,7 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 365 */ +/* 366 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -46538,7 +46561,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(366); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(367); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); @@ -46638,20 +46661,20 @@ const CleanCommand = { }; /***/ }), -/* 366 */ +/* 367 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readline = __webpack_require__(367); -const chalk = __webpack_require__(368); -const cliCursor = __webpack_require__(375); -const cliSpinners = __webpack_require__(379); -const logSymbols = __webpack_require__(381); -const stripAnsi = __webpack_require__(390); -const wcwidth = __webpack_require__(392); -const isInteractive = __webpack_require__(396); -const MuteStream = __webpack_require__(397); +const readline = __webpack_require__(368); +const chalk = __webpack_require__(369); +const cliCursor = __webpack_require__(376); +const cliSpinners = __webpack_require__(378); +const logSymbols = __webpack_require__(380); +const stripAnsi = __webpack_require__(389); +const wcwidth = __webpack_require__(391); +const isInteractive = __webpack_require__(395); +const MuteStream = __webpack_require__(396); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); @@ -47004,23 +47027,23 @@ module.exports.promise = (action, options) => { /***/ }), -/* 367 */ +/* 368 */ /***/ (function(module, exports) { module.exports = require("readline"); /***/ }), -/* 368 */ +/* 369 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiStyles = __webpack_require__(369); +const ansiStyles = __webpack_require__(370); const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex -} = __webpack_require__(373); +} = __webpack_require__(374); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -47221,7 +47244,7 @@ const chalkTag = (chalk, ...strings) => { } if (template === undefined) { - template = __webpack_require__(374); + template = __webpack_require__(375); } return template(chalk, parts.join('')); @@ -47250,7 +47273,7 @@ module.exports = chalk; /***/ }), -/* 369 */ +/* 370 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47296,7 +47319,7 @@ const setLazyProperty = (object, property, get) => { let colorConvert; const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { if (colorConvert === undefined) { - colorConvert = __webpack_require__(370); + colorConvert = __webpack_require__(371); } const offset = isBackground ? 10 : 0; @@ -47421,11 +47444,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 370 */ +/* 371 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(371); -const route = __webpack_require__(372); +const conversions = __webpack_require__(372); +const route = __webpack_require__(373); const convert = {}; @@ -47508,7 +47531,7 @@ module.exports = convert; /***/ }), -/* 371 */ +/* 372 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ @@ -48353,10 +48376,10 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 372 */ +/* 373 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(371); +const conversions = __webpack_require__(372); /* This function routes a model to all other models. @@ -48456,7 +48479,7 @@ module.exports = function (fromModel) { /***/ }), -/* 373 */ +/* 374 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48502,7 +48525,7 @@ module.exports = { /***/ }), -/* 374 */ +/* 375 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48643,12 +48666,12 @@ module.exports = (chalk, temporary) => { /***/ }), -/* 375 */ +/* 376 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(376); +const restoreCursor = __webpack_require__(377); let isHidden = false; @@ -48685,12 +48708,12 @@ exports.toggle = (force, writableStream) => { /***/ }), -/* 376 */ +/* 377 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(377); +const onetime = __webpack_require__(332); const signalExit = __webpack_require__(304); module.exports = onetime(() => { @@ -48700,63 +48723,6 @@ module.exports = onetime(() => { }); -/***/ }), -/* 377 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const mimicFn = __webpack_require__(378); - -const calledFunctions = new WeakMap(); - -const oneTime = (fn, options = {}) => { - if (typeof fn !== 'function') { - throw new TypeError('Expected a function'); - } - - let ret; - let isCalled = false; - let callCount = 0; - const functionName = fn.displayName || fn.name || ''; - - const onetime = function (...args) { - calledFunctions.set(onetime, ++callCount); - - if (isCalled) { - if (options.throw === true) { - throw new Error(`Function \`${functionName}\` can only be called once`); - } - - return ret; - } - - isCalled = true; - ret = fn.apply(this, args); - fn = null; - - return ret; - }; - - mimicFn(onetime, fn); - calledFunctions.set(onetime, callCount); - - return onetime; -}; - -module.exports = oneTime; -// TODO: Remove this for the next major release -module.exports.default = oneTime; - -module.exports.callCount = fn => { - if (!calledFunctions.has(fn)) { - throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); - } - - return calledFunctions.get(fn); -}; - - /***/ }), /* 378 */ /***/ (function(module, exports, __webpack_require__) { @@ -48764,27 +48730,7 @@ module.exports.callCount = fn => { "use strict"; -const mimicFn = (to, from) => { - for (const prop of Reflect.ownKeys(from)) { - Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); - } - - return to; -}; - -module.exports = mimicFn; -// TODO: Remove this for the next major release -module.exports.default = mimicFn; - - -/***/ }), -/* 379 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const spinners = Object.assign({}, __webpack_require__(380)); +const spinners = Object.assign({}, __webpack_require__(379)); const spinnersList = Object.keys(spinners); @@ -48802,18 +48748,18 @@ module.exports.default = spinners; /***/ }), -/* 380 */ +/* 379 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]}}"); /***/ }), -/* 381 */ +/* 380 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(382); +const chalk = __webpack_require__(381); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -48835,16 +48781,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 382 */ +/* 381 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(265); -const ansiStyles = __webpack_require__(383); -const stdoutColor = __webpack_require__(388).stdout; +const ansiStyles = __webpack_require__(382); +const stdoutColor = __webpack_require__(387).stdout; -const template = __webpack_require__(389); +const template = __webpack_require__(388); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -49070,12 +49016,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 383 */ +/* 382 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(384); +const colorConvert = __webpack_require__(383); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -49243,11 +49189,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 384 */ +/* 383 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(385); -var route = __webpack_require__(387); +var conversions = __webpack_require__(384); +var route = __webpack_require__(386); var convert = {}; @@ -49327,11 +49273,11 @@ module.exports = convert; /***/ }), -/* 385 */ +/* 384 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(386); +var cssKeywords = __webpack_require__(385); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -50201,7 +50147,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 386 */ +/* 385 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50360,10 +50306,10 @@ module.exports = { /***/ }), -/* 387 */ +/* 386 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(385); +var conversions = __webpack_require__(384); /* this function routes a model to all other models. @@ -50463,7 +50409,7 @@ module.exports = function (fromModel) { /***/ }), -/* 388 */ +/* 387 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50601,7 +50547,7 @@ module.exports = { /***/ }), -/* 389 */ +/* 388 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50736,18 +50682,18 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 390 */ +/* 389 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(391); +const ansiRegex = __webpack_require__(390); module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 391 */ +/* 390 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50764,14 +50710,14 @@ module.exports = ({onlyFirst = false} = {}) => { /***/ }), -/* 392 */ +/* 391 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var defaults = __webpack_require__(393) -var combining = __webpack_require__(395) +var defaults = __webpack_require__(392) +var combining = __webpack_require__(394) var DEFAULTS = { nul: 0, @@ -50870,10 +50816,10 @@ function bisearch(ucs) { /***/ }), -/* 393 */ +/* 392 */ /***/ (function(module, exports, __webpack_require__) { -var clone = __webpack_require__(394); +var clone = __webpack_require__(393); module.exports = function(options, defaults) { options = options || {}; @@ -50888,7 +50834,7 @@ module.exports = function(options, defaults) { }; /***/ }), -/* 394 */ +/* 393 */ /***/ (function(module, exports, __webpack_require__) { var clone = (function() { @@ -51060,7 +51006,7 @@ if ( true && module.exports) { /***/ }), -/* 395 */ +/* 394 */ /***/ (function(module, exports) { module.exports = [ @@ -51116,7 +51062,7 @@ module.exports = [ /***/ }), -/* 396 */ +/* 395 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51132,7 +51078,7 @@ module.exports = ({stream = process.stdout} = {}) => { /***/ }), -/* 397 */ +/* 396 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -51283,7 +51229,7 @@ MuteStream.prototype.close = proxy('close') /***/ }), -/* 398 */ +/* 397 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51344,7 +51290,7 @@ const RunCommand = { }; /***/ }), -/* 399 */ +/* 398 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51354,7 +51300,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(400); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(399); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51439,14 +51385,14 @@ const WatchCommand = { }; /***/ }), -/* 400 */ +/* 399 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(401); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51513,141 +51459,141 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 401 */ +/* 400 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(402); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(401); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(403); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(402); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(404); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(403); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(405); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(404); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(406); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(405); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(407); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(406); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(408); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(407); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(409); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(408); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(410); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(409); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(411); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(410); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(412); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(411); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); /* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(413); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(412); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(414); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(413); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(415); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(414); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(416); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(415); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(417); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(416); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(418); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(417); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(419); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(418); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(421); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(420); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(422); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(421); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(423); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(422); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(424); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(423); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(425); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(424); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(426); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(425); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(429); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(428); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(430); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(429); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(431); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(430); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(432); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(431); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(433); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); /* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(434); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(433); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(435); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(434); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(436); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(435); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(437); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(436); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); /* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(438); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(437); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(439); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(438); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(440); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(439); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); /* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(442); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(441); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(443); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(442); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(444); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(443); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(447); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(446); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); /* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81); @@ -51658,175 +51604,175 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(448); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(447); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(449); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(448); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(450); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(449); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(451); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(450); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); /* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(452); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(451); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(453); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(452); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(454); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(453); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(455); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(454); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(456); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(455); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(457); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(456); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(458); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(457); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(459); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(458); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(460); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(459); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(445); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(444); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(461); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(460); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(462); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(461); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(463); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(462); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(464); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(463); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); /* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(465); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(464); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(466); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(465); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(446); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(445); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(467); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(466); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(468); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(467); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(469); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(468); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(470); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(469); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(471); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(470); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(472); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(471); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(473); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(472); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(474); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(473); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(475); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(474); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(476); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(475); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(478); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(477); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(479); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(478); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(480); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(479); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(428); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(427); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(441); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(440); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(481); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(480); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(482); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(481); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(483); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(482); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(484); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(483); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(485); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(484); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(427); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(426); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(486); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(485); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(487); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(486); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(488); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(487); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(489); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(488); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(490); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(489); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(491); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(490); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(492); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(491); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(493); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(492); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(494); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(493); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(495); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(494); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(496); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(495); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(497); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(496); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(498); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(497); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -51937,7 +51883,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 402 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52016,14 +51962,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 403 */ +/* 402 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(402); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(401); /* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -52039,7 +51985,7 @@ function auditTime(duration, scheduler) { /***/ }), -/* 404 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52086,7 +52032,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 405 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52187,7 +52133,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 406 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52348,7 +52294,7 @@ function dispatchBufferClose(arg) { /***/ }), -/* 407 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52467,7 +52413,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 408 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52560,7 +52506,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 409 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52620,7 +52566,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 410 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52636,7 +52582,7 @@ function combineAll(project) { /***/ }), -/* 411 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52668,7 +52614,7 @@ function combineLatest() { /***/ }), -/* 412 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52688,7 +52634,7 @@ function concat() { /***/ }), -/* 413 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52704,13 +52650,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 414 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(413); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(412); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -52720,7 +52666,7 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 415 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52785,7 +52731,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 416 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52870,7 +52816,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 417 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52946,7 +52892,7 @@ function dispatchNext(subscriber) { /***/ }), -/* 418 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52996,7 +52942,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 419 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53004,7 +52950,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(420); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); /* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -53103,7 +53049,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 420 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53117,7 +53063,7 @@ function isDate(value) { /***/ }), -/* 421 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53263,7 +53209,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 422 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53301,7 +53247,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 423 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53377,7 +53323,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 424 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53448,13 +53394,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 425 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(424); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(423); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -53464,7 +53410,7 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 426 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53472,9 +53418,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); /* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(427); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(428); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(427); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -53496,7 +53442,7 @@ function elementAt(index, defaultValue) { /***/ }), -/* 427 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53562,7 +53508,7 @@ function defaultErrorFactory() { /***/ }), -/* 428 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53624,7 +53570,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 429 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53646,7 +53592,7 @@ function endWith() { /***/ }), -/* 430 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53708,7 +53654,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 431 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53762,7 +53708,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 432 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53856,7 +53802,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 433 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53968,7 +53914,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 434 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54006,7 +53952,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 435 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54078,13 +54024,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 436 */ +/* 435 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(435); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(434); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -54094,7 +54040,7 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 437 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54102,9 +54048,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(428); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(427); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(427); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(426); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -54121,7 +54067,7 @@ function first(predicate, defaultValue) { /***/ }), -/* 438 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54158,7 +54104,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 439 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54202,7 +54148,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 440 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54210,9 +54156,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(441); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(427); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(418); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(440); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(426); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(417); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -54229,7 +54175,7 @@ function last(predicate, defaultValue) { /***/ }), -/* 441 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54306,7 +54252,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 442 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54345,7 +54291,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 443 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54395,13 +54341,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 444 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -54414,15 +54360,15 @@ function max(comparer) { /***/ }), -/* 445 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(446); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(441); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(440); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); /* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -54443,7 +54389,7 @@ function reduce(accumulator, seed) { /***/ }), -/* 446 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54525,7 +54471,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 447 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54545,7 +54491,7 @@ function merge() { /***/ }), -/* 448 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54570,7 +54516,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 449 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54679,13 +54625,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 450 */ +/* 449 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -54698,7 +54644,7 @@ function min(comparer) { /***/ }), -/* 451 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54747,7 +54693,7 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 452 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54837,7 +54783,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 453 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54885,7 +54831,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 454 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54908,7 +54854,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 455 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54948,14 +54894,14 @@ function plucker(props, length) { /***/ }), -/* 456 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(27); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -54968,14 +54914,14 @@ function publish(selector) { /***/ }), -/* 457 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); /* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(32); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -54986,14 +54932,14 @@ function publishBehavior(value) { /***/ }), -/* 458 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); /* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(50); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -55004,14 +54950,14 @@ function publishLast() { /***/ }), -/* 459 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); /* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(33); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -55027,7 +54973,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 460 */ +/* 459 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55054,7 +55000,7 @@ function race() { /***/ }), -/* 461 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55119,7 +55065,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 462 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55213,7 +55159,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 463 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55266,7 +55212,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 464 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55352,7 +55298,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 465 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55407,7 +55353,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 466 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55467,7 +55413,7 @@ function dispatchNotification(state) { /***/ }), -/* 467 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55590,13 +55536,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 468 */ +/* 467 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(450); /* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); /** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ @@ -55613,7 +55559,7 @@ function share() { /***/ }), -/* 469 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55682,7 +55628,7 @@ function shareReplayOperator(_a) { /***/ }), -/* 470 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55762,7 +55708,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 471 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55804,7 +55750,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 472 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55866,7 +55812,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 473 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55923,7 +55869,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 474 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55979,7 +55925,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 475 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56008,13 +55954,13 @@ function startWith() { /***/ }), -/* 476 */ +/* 475 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(477); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(476); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -56039,7 +55985,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 477 */ +/* 476 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56103,13 +56049,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 478 */ +/* 477 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(479); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(478); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -56121,7 +56067,7 @@ function switchAll() { /***/ }), -/* 479 */ +/* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56209,13 +56155,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 480 */ +/* 479 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(479); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(478); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -56225,7 +56171,7 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 481 */ +/* 480 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56273,7 +56219,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 482 */ +/* 481 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56341,7 +56287,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 483 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56429,7 +56375,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 484 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56531,7 +56477,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 485 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56540,7 +56486,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(484); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(483); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -56629,7 +56575,7 @@ function dispatchNext(arg) { /***/ }), -/* 486 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56637,7 +56583,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(446); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(445); /* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91); /* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -56673,7 +56619,7 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 487 */ +/* 486 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56681,7 +56627,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); /* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(488); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(487); /* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49); /** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ @@ -56698,7 +56644,7 @@ function timeout(due, scheduler) { /***/ }), -/* 488 */ +/* 487 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56706,7 +56652,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(420); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419); /* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */ @@ -56777,7 +56723,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 489 */ +/* 488 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56807,13 +56753,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 490 */ +/* 489 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -56830,7 +56776,7 @@ function toArray() { /***/ }), -/* 491 */ +/* 490 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56908,7 +56854,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 492 */ +/* 491 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56998,7 +56944,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 493 */ +/* 492 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57168,7 +57114,7 @@ function dispatchWindowClose(state) { /***/ }), -/* 494 */ +/* 493 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57311,7 +57257,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 495 */ +/* 494 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57408,7 +57354,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 496 */ +/* 495 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57503,7 +57449,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 497 */ +/* 496 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57525,7 +57471,7 @@ function zip() { /***/ }), -/* 498 */ +/* 497 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57541,7 +57487,7 @@ function zipAll(project) { /***/ }), -/* 499 */ +/* 498 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57550,8 +57496,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(364); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(500); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(365); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(499); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -57633,7 +57579,7 @@ function toArray(value) { } /***/ }), -/* 500 */ +/* 499 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57641,13 +57587,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(501); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(500); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(359); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(360); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(504); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -57809,15 +57755,15 @@ class Kibana { } /***/ }), -/* 501 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(150); -const arrayUnion = __webpack_require__(502); -const arrayDiffer = __webpack_require__(503); -const arrify = __webpack_require__(504); +const arrayUnion = __webpack_require__(501); +const arrayDiffer = __webpack_require__(502); +const arrify = __webpack_require__(503); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -57841,7 +57787,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 502 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57853,7 +57799,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 503 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57868,7 +57814,7 @@ module.exports = arrayDiffer; /***/ }), -/* 504 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57898,7 +57844,7 @@ module.exports = arrify; /***/ }), -/* 505 */ +/* 504 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57968,12 +57914,12 @@ function getProjectPaths({ } /***/ }), -/* 506 */ +/* 505 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(507); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(506); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); /* @@ -57997,19 +57943,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 507 */ +/* 506 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(508); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(507); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(505); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(504); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); @@ -58146,7 +58092,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 508 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58154,14 +58100,14 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(156); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pMap = __webpack_require__(509); -const arrify = __webpack_require__(510); -const globby = __webpack_require__(511); -const hasGlob = __webpack_require__(707); -const cpFile = __webpack_require__(709); -const junk = __webpack_require__(719); -const pFilter = __webpack_require__(720); -const CpyError = __webpack_require__(722); +const pMap = __webpack_require__(508); +const arrify = __webpack_require__(503); +const globby = __webpack_require__(509); +const hasGlob = __webpack_require__(705); +const cpFile = __webpack_require__(707); +const junk = __webpack_require__(717); +const pFilter = __webpack_require__(718); +const CpyError = __webpack_require__(720); const defaultOptions = { ignoreJunk: true @@ -58312,7 +58258,7 @@ module.exports = (source, destination, { /***/ }), -/* 509 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58400,47 +58346,17 @@ module.exports = async ( /***/ }), -/* 510 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const arrify = value => { - if (value === null || value === undefined) { - return []; - } - - if (Array.isArray(value)) { - return value; - } - - if (typeof value === 'string') { - return [value]; - } - - if (typeof value[Symbol.iterator] === 'function') { - return [...value]; - } - - return [value]; -}; - -module.exports = arrify; - - -/***/ }), -/* 511 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(512); +const arrayUnion = __webpack_require__(510); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(514); -const dirGlob = __webpack_require__(700); -const gitignore = __webpack_require__(703); +const fastGlob = __webpack_require__(512); +const dirGlob = __webpack_require__(698); +const gitignore = __webpack_require__(701); const DEFAULT_FILTER = () => false; @@ -58585,12 +58501,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 512 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(513); +var arrayUniq = __webpack_require__(511); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -58598,7 +58514,7 @@ module.exports = function () { /***/ }), -/* 513 */ +/* 511 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58667,10 +58583,10 @@ if ('Set' in global) { /***/ }), -/* 514 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(515); +const pkg = __webpack_require__(513); module.exports = pkg.async; module.exports.default = pkg.async; @@ -58683,19 +58599,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 515 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(516); -var taskManager = __webpack_require__(517); -var reader_async_1 = __webpack_require__(671); -var reader_stream_1 = __webpack_require__(695); -var reader_sync_1 = __webpack_require__(696); -var arrayUtils = __webpack_require__(698); -var streamUtils = __webpack_require__(699); +var optionsManager = __webpack_require__(514); +var taskManager = __webpack_require__(515); +var reader_async_1 = __webpack_require__(669); +var reader_stream_1 = __webpack_require__(693); +var reader_sync_1 = __webpack_require__(694); +var arrayUtils = __webpack_require__(696); +var streamUtils = __webpack_require__(697); /** * Synchronous API. */ @@ -58761,7 +58677,7 @@ function isString(source) { /***/ }), -/* 516 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58799,13 +58715,13 @@ exports.prepare = prepare; /***/ }), -/* 517 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(518); +var patternUtils = __webpack_require__(516); /** * Generate tasks based on parent directory of each pattern. */ @@ -58896,16 +58812,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 518 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(519); +var globParent = __webpack_require__(517); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(522); +var micromatch = __webpack_require__(520); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -59051,15 +58967,15 @@ exports.matchAny = matchAny; /***/ }), -/* 519 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(520); -var pathDirname = __webpack_require__(521); +var isglob = __webpack_require__(518); +var pathDirname = __webpack_require__(519); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -59082,7 +58998,7 @@ module.exports = function globParent(str) { /***/ }), -/* 520 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -59113,7 +59029,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 521 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59263,7 +59179,7 @@ module.exports.win32 = win32; /***/ }), -/* 522 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59274,18 +59190,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(523); -var toRegex = __webpack_require__(524); -var extend = __webpack_require__(637); +var braces = __webpack_require__(521); +var toRegex = __webpack_require__(522); +var extend = __webpack_require__(635); /** * Local dependencies */ -var compilers = __webpack_require__(639); -var parsers = __webpack_require__(666); -var cache = __webpack_require__(667); -var utils = __webpack_require__(668); +var compilers = __webpack_require__(637); +var parsers = __webpack_require__(664); +var cache = __webpack_require__(665); +var utils = __webpack_require__(666); var MAX_LENGTH = 1024 * 64; /** @@ -60147,7 +60063,7 @@ module.exports = micromatch; /***/ }), -/* 523 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60157,18 +60073,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(524); -var unique = __webpack_require__(546); -var extend = __webpack_require__(547); +var toRegex = __webpack_require__(522); +var unique = __webpack_require__(544); +var extend = __webpack_require__(545); /** * Local dependencies */ -var compilers = __webpack_require__(549); -var parsers = __webpack_require__(562); -var Braces = __webpack_require__(566); -var utils = __webpack_require__(550); +var compilers = __webpack_require__(547); +var parsers = __webpack_require__(560); +var Braces = __webpack_require__(564); +var utils = __webpack_require__(548); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -60472,16 +60388,16 @@ module.exports = braces; /***/ }), -/* 524 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(525); -var define = __webpack_require__(531); -var extend = __webpack_require__(539); -var not = __webpack_require__(543); +var safe = __webpack_require__(523); +var define = __webpack_require__(529); +var extend = __webpack_require__(537); +var not = __webpack_require__(541); var MAX_LENGTH = 1024 * 64; /** @@ -60634,10 +60550,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 525 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(526); +var parse = __webpack_require__(524); var types = parse.types; module.exports = function (re, opts) { @@ -60683,13 +60599,13 @@ function isRegExp (x) { /***/ }), -/* 526 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(527); -var types = __webpack_require__(528); -var sets = __webpack_require__(529); -var positions = __webpack_require__(530); +var util = __webpack_require__(525); +var types = __webpack_require__(526); +var sets = __webpack_require__(527); +var positions = __webpack_require__(528); module.exports = function(regexpStr) { @@ -60971,11 +60887,11 @@ module.exports.types = types; /***/ }), -/* 527 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); -var sets = __webpack_require__(529); +var types = __webpack_require__(526); +var sets = __webpack_require__(527); // All of these are private and only used by randexp. @@ -61088,7 +61004,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 528 */ +/* 526 */ /***/ (function(module, exports) { module.exports = { @@ -61104,10 +61020,10 @@ module.exports = { /***/ }), -/* 529 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); +var types = __webpack_require__(526); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -61192,10 +61108,10 @@ exports.anyChar = function() { /***/ }), -/* 530 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); +var types = __webpack_require__(526); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -61215,7 +61131,7 @@ exports.end = function() { /***/ }), -/* 531 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61228,8 +61144,8 @@ exports.end = function() { -var isobject = __webpack_require__(532); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(530); +var isDescriptor = __webpack_require__(531); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -61260,7 +61176,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 532 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61279,7 +61195,7 @@ module.exports = function isObject(val) { /***/ }), -/* 533 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61292,9 +61208,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(534); -var isAccessor = __webpack_require__(535); -var isData = __webpack_require__(537); +var typeOf = __webpack_require__(532); +var isAccessor = __webpack_require__(533); +var isData = __webpack_require__(535); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -61308,7 +61224,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 534 */ +/* 532 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61443,7 +61359,7 @@ function isBuffer(val) { /***/ }), -/* 535 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61456,7 +61372,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(536); +var typeOf = __webpack_require__(534); // accessor descriptor properties var accessor = { @@ -61519,7 +61435,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 536 */ +/* 534 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61654,7 +61570,7 @@ function isBuffer(val) { /***/ }), -/* 537 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61667,7 +61583,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(538); +var typeOf = __webpack_require__(536); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -61710,7 +61626,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 538 */ +/* 536 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61845,14 +61761,14 @@ function isBuffer(val) { /***/ }), -/* 539 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(540); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(538); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -61912,7 +61828,7 @@ function isEnum(obj, key) { /***/ }), -/* 540 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61925,7 +61841,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -61933,7 +61849,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 541 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61946,7 +61862,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(530); function isObjectObject(o) { return isObject(o) === true @@ -61977,7 +61893,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 542 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62024,14 +61940,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 543 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(544); -var safe = __webpack_require__(525); +var extend = __webpack_require__(542); +var safe = __webpack_require__(523); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -62103,14 +62019,14 @@ module.exports = toRegex; /***/ }), -/* 544 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(545); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(543); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -62170,7 +62086,7 @@ function isEnum(obj, key) { /***/ }), -/* 545 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62183,7 +62099,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -62191,7 +62107,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 546 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62241,13 +62157,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 547 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(548); +var isObject = __webpack_require__(546); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -62281,7 +62197,7 @@ function hasOwn(obj, key) { /***/ }), -/* 548 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62301,13 +62217,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 549 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(550); +var utils = __webpack_require__(548); module.exports = function(braces, options) { braces.compiler @@ -62590,25 +62506,25 @@ function hasQueue(node) { /***/ }), -/* 550 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(551); +var splitString = __webpack_require__(549); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(547); -utils.flatten = __webpack_require__(554); -utils.isObject = __webpack_require__(532); -utils.fillRange = __webpack_require__(555); -utils.repeat = __webpack_require__(561); -utils.unique = __webpack_require__(546); +utils.extend = __webpack_require__(545); +utils.flatten = __webpack_require__(552); +utils.isObject = __webpack_require__(530); +utils.fillRange = __webpack_require__(553); +utils.repeat = __webpack_require__(559); +utils.unique = __webpack_require__(544); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -62940,7 +62856,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 551 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62953,7 +62869,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(552); +var extend = __webpack_require__(550); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -63118,14 +63034,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 552 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(553); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(551); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63185,7 +63101,7 @@ function isEnum(obj, key) { /***/ }), -/* 553 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63198,7 +63114,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63206,7 +63122,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 554 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63235,7 +63151,7 @@ function flat(arr, res) { /***/ }), -/* 555 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63249,10 +63165,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(556); -var extend = __webpack_require__(547); -var repeat = __webpack_require__(559); -var toRegex = __webpack_require__(560); +var isNumber = __webpack_require__(554); +var extend = __webpack_require__(545); +var repeat = __webpack_require__(557); +var toRegex = __webpack_require__(558); /** * Return a range of numbers or letters. @@ -63450,7 +63366,7 @@ module.exports = fillRange; /***/ }), -/* 556 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63463,7 +63379,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(557); +var typeOf = __webpack_require__(555); module.exports = function isNumber(num) { var type = typeOf(num); @@ -63479,10 +63395,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 557 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -63601,7 +63517,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 558 */ +/* 556 */ /***/ (function(module, exports) { /*! @@ -63628,7 +63544,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 559 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63705,7 +63621,7 @@ function repeat(str, num) { /***/ }), -/* 560 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63718,8 +63634,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(559); -var isNumber = __webpack_require__(556); +var repeat = __webpack_require__(557); +var isNumber = __webpack_require__(554); var cache = {}; function toRegexRange(min, max, options) { @@ -64006,7 +63922,7 @@ module.exports = toRegexRange; /***/ }), -/* 561 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64031,14 +63947,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 562 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(563); -var utils = __webpack_require__(550); +var Node = __webpack_require__(561); +var utils = __webpack_require__(548); /** * Braces parsers @@ -64398,15 +64314,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 563 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(532); -var define = __webpack_require__(564); -var utils = __webpack_require__(565); +var isObject = __webpack_require__(530); +var define = __webpack_require__(562); +var utils = __webpack_require__(563); var ownNames; /** @@ -64897,7 +64813,7 @@ exports = module.exports = Node; /***/ }), -/* 564 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64910,7 +64826,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(531); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -64935,13 +64851,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 565 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(557); +var typeOf = __webpack_require__(555); var utils = module.exports; /** @@ -65961,17 +65877,17 @@ function assert(val, message) { /***/ }), -/* 566 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(547); -var Snapdragon = __webpack_require__(567); -var compilers = __webpack_require__(549); -var parsers = __webpack_require__(562); -var utils = __webpack_require__(550); +var extend = __webpack_require__(545); +var Snapdragon = __webpack_require__(565); +var compilers = __webpack_require__(547); +var parsers = __webpack_require__(560); +var utils = __webpack_require__(548); /** * Customize Snapdragon parser and renderer @@ -66072,17 +65988,17 @@ module.exports = Braces; /***/ }), -/* 567 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(568); -var define = __webpack_require__(595); -var Compiler = __webpack_require__(605); -var Parser = __webpack_require__(634); -var utils = __webpack_require__(614); +var Base = __webpack_require__(566); +var define = __webpack_require__(593); +var Compiler = __webpack_require__(603); +var Parser = __webpack_require__(632); +var utils = __webpack_require__(612); var regexCache = {}; var cache = {}; @@ -66253,20 +66169,20 @@ module.exports.Parser = Parser; /***/ }), -/* 568 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(569); -var CacheBase = __webpack_require__(570); -var Emitter = __webpack_require__(571); -var isObject = __webpack_require__(532); -var merge = __webpack_require__(589); -var pascal = __webpack_require__(592); -var cu = __webpack_require__(593); +var define = __webpack_require__(567); +var CacheBase = __webpack_require__(568); +var Emitter = __webpack_require__(569); +var isObject = __webpack_require__(530); +var merge = __webpack_require__(587); +var pascal = __webpack_require__(590); +var cu = __webpack_require__(591); /** * Optionally define a custom `cache` namespace to use. @@ -66695,7 +66611,7 @@ module.exports.namespace = namespace; /***/ }), -/* 569 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66708,7 +66624,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(531); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66733,21 +66649,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 570 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(532); -var Emitter = __webpack_require__(571); -var visit = __webpack_require__(572); -var toPath = __webpack_require__(575); -var union = __webpack_require__(576); -var del = __webpack_require__(580); -var get = __webpack_require__(578); -var has = __webpack_require__(585); -var set = __webpack_require__(588); +var isObject = __webpack_require__(530); +var Emitter = __webpack_require__(569); +var visit = __webpack_require__(570); +var toPath = __webpack_require__(573); +var union = __webpack_require__(574); +var del = __webpack_require__(578); +var get = __webpack_require__(576); +var has = __webpack_require__(583); +var set = __webpack_require__(586); /** * Create a `Cache` constructor that when instantiated will @@ -67001,7 +66917,7 @@ module.exports.namespace = namespace; /***/ }), -/* 571 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { @@ -67170,7 +67086,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 572 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67183,8 +67099,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(573); -var mapVisit = __webpack_require__(574); +var visit = __webpack_require__(571); +var mapVisit = __webpack_require__(572); module.exports = function(collection, method, val) { var result; @@ -67207,7 +67123,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 573 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67220,7 +67136,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(530); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -67247,14 +67163,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 574 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(573); +var visit = __webpack_require__(571); /** * Map `visit` over an array of objects. @@ -67291,7 +67207,7 @@ function isObject(val) { /***/ }), -/* 575 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67304,7 +67220,7 @@ function isObject(val) { -var typeOf = __webpack_require__(557); +var typeOf = __webpack_require__(555); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -67331,16 +67247,16 @@ function filter(arr) { /***/ }), -/* 576 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(548); -var union = __webpack_require__(577); -var get = __webpack_require__(578); -var set = __webpack_require__(579); +var isObject = __webpack_require__(546); +var union = __webpack_require__(575); +var get = __webpack_require__(576); +var set = __webpack_require__(577); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -67368,7 +67284,7 @@ function arrayify(val) { /***/ }), -/* 577 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67404,7 +67320,7 @@ module.exports = function union(init) { /***/ }), -/* 578 */ +/* 576 */ /***/ (function(module, exports) { /*! @@ -67460,7 +67376,7 @@ function toString(val) { /***/ }), -/* 579 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67473,10 +67389,10 @@ function toString(val) { -var split = __webpack_require__(551); -var extend = __webpack_require__(547); -var isPlainObject = __webpack_require__(541); -var isObject = __webpack_require__(548); +var split = __webpack_require__(549); +var extend = __webpack_require__(545); +var isPlainObject = __webpack_require__(539); +var isObject = __webpack_require__(546); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -67522,7 +67438,7 @@ function isValidKey(key) { /***/ }), -/* 580 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67535,8 +67451,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(532); -var has = __webpack_require__(581); +var isObject = __webpack_require__(530); +var has = __webpack_require__(579); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -67561,7 +67477,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 581 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67574,9 +67490,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(582); -var hasValues = __webpack_require__(584); -var get = __webpack_require__(578); +var isObject = __webpack_require__(580); +var hasValues = __webpack_require__(582); +var get = __webpack_require__(576); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -67587,7 +67503,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 582 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67600,7 +67516,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(583); +var isArray = __webpack_require__(581); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -67608,7 +67524,7 @@ module.exports = function isObject(val) { /***/ }), -/* 583 */ +/* 581 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -67619,7 +67535,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 584 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67662,7 +67578,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 585 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67675,9 +67591,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(532); -var hasValues = __webpack_require__(586); -var get = __webpack_require__(578); +var isObject = __webpack_require__(530); +var hasValues = __webpack_require__(584); +var get = __webpack_require__(576); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -67685,7 +67601,7 @@ module.exports = function(val, prop) { /***/ }), -/* 586 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67698,8 +67614,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(587); -var isNumber = __webpack_require__(556); +var typeOf = __webpack_require__(585); +var isNumber = __webpack_require__(554); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -67752,10 +67668,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 587 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -67877,7 +67793,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 588 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67890,10 +67806,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(551); -var extend = __webpack_require__(547); -var isPlainObject = __webpack_require__(541); -var isObject = __webpack_require__(548); +var split = __webpack_require__(549); +var extend = __webpack_require__(545); +var isPlainObject = __webpack_require__(539); +var isObject = __webpack_require__(546); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -67939,14 +67855,14 @@ function isValidKey(key) { /***/ }), -/* 589 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(590); -var forIn = __webpack_require__(591); +var isExtendable = __webpack_require__(588); +var forIn = __webpack_require__(589); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -68010,7 +67926,7 @@ module.exports = mixinDeep; /***/ }), -/* 590 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68023,7 +67939,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -68031,7 +67947,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 591 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68054,7 +67970,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 592 */ +/* 590 */ /***/ (function(module, exports) { /*! @@ -68081,14 +67997,14 @@ module.exports = pascalcase; /***/ }), -/* 593 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(594); +var utils = __webpack_require__(592); /** * Expose class utils @@ -68453,7 +68369,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 594 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68467,10 +68383,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(577); -utils.define = __webpack_require__(595); -utils.isObj = __webpack_require__(532); -utils.staticExtend = __webpack_require__(602); +utils.union = __webpack_require__(575); +utils.define = __webpack_require__(593); +utils.isObj = __webpack_require__(530); +utils.staticExtend = __webpack_require__(600); /** @@ -68481,7 +68397,7 @@ module.exports = utils; /***/ }), -/* 595 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68494,7 +68410,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(596); +var isDescriptor = __webpack_require__(594); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68519,7 +68435,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 596 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68532,9 +68448,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(597); -var isAccessor = __webpack_require__(598); -var isData = __webpack_require__(600); +var typeOf = __webpack_require__(595); +var isAccessor = __webpack_require__(596); +var isData = __webpack_require__(598); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -68548,7 +68464,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 597 */ +/* 595 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -68701,7 +68617,7 @@ function isBuffer(val) { /***/ }), -/* 598 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68714,7 +68630,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(599); +var typeOf = __webpack_require__(597); // accessor descriptor properties var accessor = { @@ -68777,10 +68693,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 599 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -68899,7 +68815,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 600 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68912,7 +68828,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(601); +var typeOf = __webpack_require__(599); // data descriptor properties var data = { @@ -68961,10 +68877,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 601 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -69083,7 +68999,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 602 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69096,8 +69012,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(603); -var define = __webpack_require__(595); +var copy = __webpack_require__(601); +var define = __webpack_require__(593); var util = __webpack_require__(112); /** @@ -69180,15 +69096,15 @@ module.exports = extend; /***/ }), -/* 603 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(557); -var copyDescriptor = __webpack_require__(604); -var define = __webpack_require__(595); +var typeOf = __webpack_require__(555); +var copyDescriptor = __webpack_require__(602); +var define = __webpack_require__(593); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -69361,7 +69277,7 @@ module.exports.has = has; /***/ }), -/* 604 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69449,16 +69365,16 @@ function isObject(val) { /***/ }), -/* 605 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(606); -var define = __webpack_require__(595); -var debug = __webpack_require__(608)('snapdragon:compiler'); -var utils = __webpack_require__(614); +var use = __webpack_require__(604); +var define = __webpack_require__(593); +var debug = __webpack_require__(606)('snapdragon:compiler'); +var utils = __webpack_require__(612); /** * Create a new `Compiler` with the given `options`. @@ -69612,7 +69528,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(633); + var sourcemaps = __webpack_require__(631); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -69633,7 +69549,7 @@ module.exports = Compiler; /***/ }), -/* 606 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69646,7 +69562,7 @@ module.exports = Compiler; -var utils = __webpack_require__(607); +var utils = __webpack_require__(605); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -69761,7 +69677,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 607 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69775,8 +69691,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(595); -utils.isObject = __webpack_require__(532); +utils.define = __webpack_require__(593); +utils.isObject = __webpack_require__(530); utils.isString = function(val) { @@ -69791,7 +69707,7 @@ module.exports = utils; /***/ }), -/* 608 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69800,14 +69716,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(609); + module.exports = __webpack_require__(607); } else { - module.exports = __webpack_require__(612); + module.exports = __webpack_require__(610); } /***/ }), -/* 609 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69816,7 +69732,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(610); +exports = module.exports = __webpack_require__(608); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -69998,7 +69914,7 @@ function localstorage() { /***/ }), -/* 610 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { @@ -70014,7 +69930,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(611); +exports.humanize = __webpack_require__(609); /** * The currently active debug mode names, and names to skip. @@ -70206,7 +70122,7 @@ function coerce(val) { /***/ }), -/* 611 */ +/* 609 */ /***/ (function(module, exports) { /** @@ -70364,7 +70280,7 @@ function plural(ms, n, name) { /***/ }), -/* 612 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -70380,7 +70296,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(610); +exports = module.exports = __webpack_require__(608); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -70559,7 +70475,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(613); + var net = __webpack_require__(611); stream = new net.Socket({ fd: fd, readable: false, @@ -70618,13 +70534,13 @@ exports.enable(load()); /***/ }), -/* 613 */ +/* 611 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 614 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70634,9 +70550,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(547); -exports.SourceMap = __webpack_require__(615); -exports.sourceMapResolve = __webpack_require__(626); +exports.extend = __webpack_require__(545); +exports.SourceMap = __webpack_require__(613); +exports.sourceMapResolve = __webpack_require__(624); /** * Convert backslash in the given string to forward slashes @@ -70679,7 +70595,7 @@ exports.last = function(arr, n) { /***/ }), -/* 615 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -70687,13 +70603,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(616).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(622).SourceMapConsumer; -exports.SourceNode = __webpack_require__(625).SourceNode; +exports.SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(620).SourceMapConsumer; +exports.SourceNode = __webpack_require__(623).SourceNode; /***/ }), -/* 616 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -70703,10 +70619,10 @@ exports.SourceNode = __webpack_require__(625).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(617); -var util = __webpack_require__(619); -var ArraySet = __webpack_require__(620).ArraySet; -var MappingList = __webpack_require__(621).MappingList; +var base64VLQ = __webpack_require__(615); +var util = __webpack_require__(617); +var ArraySet = __webpack_require__(618).ArraySet; +var MappingList = __webpack_require__(619).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -71115,7 +71031,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 617 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71155,7 +71071,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(618); +var base64 = __webpack_require__(616); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -71261,7 +71177,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 618 */ +/* 616 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71334,7 +71250,7 @@ exports.decode = function (charCode) { /***/ }), -/* 619 */ +/* 617 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71757,7 +71673,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 620 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71767,7 +71683,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(619); +var util = __webpack_require__(617); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -71884,7 +71800,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 621 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71894,7 +71810,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(619); +var util = __webpack_require__(617); /** * Determine whether mappingB is after mappingA with respect to generated @@ -71969,7 +71885,7 @@ exports.MappingList = MappingList; /***/ }), -/* 622 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71979,11 +71895,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(619); -var binarySearch = __webpack_require__(623); -var ArraySet = __webpack_require__(620).ArraySet; -var base64VLQ = __webpack_require__(617); -var quickSort = __webpack_require__(624).quickSort; +var util = __webpack_require__(617); +var binarySearch = __webpack_require__(621); +var ArraySet = __webpack_require__(618).ArraySet; +var base64VLQ = __webpack_require__(615); +var quickSort = __webpack_require__(622).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -73057,7 +72973,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 623 */ +/* 621 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73174,7 +73090,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 624 */ +/* 622 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73294,7 +73210,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 625 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73304,8 +73220,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(616).SourceMapGenerator; -var util = __webpack_require__(619); +var SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; +var util = __webpack_require__(617); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -73713,17 +73629,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 626 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(627) -var resolveUrl = __webpack_require__(628) -var decodeUriComponent = __webpack_require__(629) -var urix = __webpack_require__(631) -var atob = __webpack_require__(632) +var sourceMappingURL = __webpack_require__(625) +var resolveUrl = __webpack_require__(626) +var decodeUriComponent = __webpack_require__(627) +var urix = __webpack_require__(629) +var atob = __webpack_require__(630) @@ -74021,7 +73937,7 @@ module.exports = { /***/ }), -/* 627 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -74084,7 +74000,7 @@ void (function(root, factory) { /***/ }), -/* 628 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -74102,13 +74018,13 @@ module.exports = resolveUrl /***/ }), -/* 629 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(630) +var decodeUriComponent = __webpack_require__(628) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -74119,7 +74035,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 630 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74220,7 +74136,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 631 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -74243,7 +74159,7 @@ module.exports = urix /***/ }), -/* 632 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74257,7 +74173,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 633 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74265,8 +74181,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(595); -var utils = __webpack_require__(614); +var define = __webpack_require__(593); +var utils = __webpack_require__(612); /** * Expose `mixin()`. @@ -74409,19 +74325,19 @@ exports.comment = function(node) { /***/ }), -/* 634 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(606); +var use = __webpack_require__(604); var util = __webpack_require__(112); -var Cache = __webpack_require__(635); -var define = __webpack_require__(595); -var debug = __webpack_require__(608)('snapdragon:parser'); -var Position = __webpack_require__(636); -var utils = __webpack_require__(614); +var Cache = __webpack_require__(633); +var define = __webpack_require__(593); +var debug = __webpack_require__(606)('snapdragon:parser'); +var Position = __webpack_require__(634); +var utils = __webpack_require__(612); /** * Create a new `Parser` with the given `input` and `options`. @@ -74949,7 +74865,7 @@ module.exports = Parser; /***/ }), -/* 635 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75056,13 +74972,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 636 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(595); +var define = __webpack_require__(593); /** * Store position for a node @@ -75077,14 +74993,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 637 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(638); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(636); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -75144,7 +75060,7 @@ function isEnum(obj, key) { /***/ }), -/* 638 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75157,7 +75073,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -75165,14 +75081,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 639 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(640); -var extglob = __webpack_require__(655); +var nanomatch = __webpack_require__(638); +var extglob = __webpack_require__(653); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -75249,7 +75165,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 640 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75260,17 +75176,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(524); -var extend = __webpack_require__(641); +var toRegex = __webpack_require__(522); +var extend = __webpack_require__(639); /** * Local dependencies */ -var compilers = __webpack_require__(643); -var parsers = __webpack_require__(644); -var cache = __webpack_require__(647); -var utils = __webpack_require__(649); +var compilers = __webpack_require__(641); +var parsers = __webpack_require__(642); +var cache = __webpack_require__(645); +var utils = __webpack_require__(647); var MAX_LENGTH = 1024 * 64; /** @@ -76094,14 +76010,14 @@ module.exports = nanomatch; /***/ }), -/* 641 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(642); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(640); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -76161,7 +76077,7 @@ function isEnum(obj, key) { /***/ }), -/* 642 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76174,7 +76090,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -76182,7 +76098,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 643 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76528,15 +76444,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 644 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(543); -var toRegex = __webpack_require__(524); -var isOdd = __webpack_require__(645); +var regexNot = __webpack_require__(541); +var toRegex = __webpack_require__(522); +var isOdd = __webpack_require__(643); /** * Characters to use in negation regex (we want to "not" match @@ -76922,7 +76838,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 645 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76935,7 +76851,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(646); +var isNumber = __webpack_require__(644); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -76949,7 +76865,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 646 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76977,14 +76893,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 647 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(648))(); +module.exports = new (__webpack_require__(646))(); /***/ }), -/* 648 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76997,7 +76913,7 @@ module.exports = new (__webpack_require__(648))(); -var MapCache = __webpack_require__(635); +var MapCache = __webpack_require__(633); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -77119,7 +77035,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 649 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77132,14 +77048,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(650)(); -var Snapdragon = __webpack_require__(567); -utils.define = __webpack_require__(651); -utils.diff = __webpack_require__(652); -utils.extend = __webpack_require__(641); -utils.pick = __webpack_require__(653); -utils.typeOf = __webpack_require__(654); -utils.unique = __webpack_require__(546); +var isWindows = __webpack_require__(648)(); +var Snapdragon = __webpack_require__(565); +utils.define = __webpack_require__(649); +utils.diff = __webpack_require__(650); +utils.extend = __webpack_require__(639); +utils.pick = __webpack_require__(651); +utils.typeOf = __webpack_require__(652); +utils.unique = __webpack_require__(544); /** * Returns true if the given value is effectively an empty string @@ -77505,7 +77421,7 @@ utils.unixify = function(options) { /***/ }), -/* 650 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -77533,7 +77449,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 651 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77546,8 +77462,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(532); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(530); +var isDescriptor = __webpack_require__(531); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -77578,7 +77494,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 652 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77632,7 +77548,7 @@ function diffArray(one, two) { /***/ }), -/* 653 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77645,7 +77561,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(530); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -77674,7 +77590,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 654 */ +/* 652 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -77809,7 +77725,7 @@ function isBuffer(val) { /***/ }), -/* 655 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77819,18 +77735,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(547); -var unique = __webpack_require__(546); -var toRegex = __webpack_require__(524); +var extend = __webpack_require__(545); +var unique = __webpack_require__(544); +var toRegex = __webpack_require__(522); /** * Local dependencies */ -var compilers = __webpack_require__(656); -var parsers = __webpack_require__(662); -var Extglob = __webpack_require__(665); -var utils = __webpack_require__(664); +var compilers = __webpack_require__(654); +var parsers = __webpack_require__(660); +var Extglob = __webpack_require__(663); +var utils = __webpack_require__(662); var MAX_LENGTH = 1024 * 64; /** @@ -78147,13 +78063,13 @@ module.exports = extglob; /***/ }), -/* 656 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(657); +var brackets = __webpack_require__(655); /** * Extglob compilers @@ -78323,7 +78239,7 @@ module.exports = function(extglob) { /***/ }), -/* 657 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78333,17 +78249,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(658); -var parsers = __webpack_require__(660); +var compilers = __webpack_require__(656); +var parsers = __webpack_require__(658); /** * Module dependencies */ -var debug = __webpack_require__(608)('expand-brackets'); -var extend = __webpack_require__(547); -var Snapdragon = __webpack_require__(567); -var toRegex = __webpack_require__(524); +var debug = __webpack_require__(606)('expand-brackets'); +var extend = __webpack_require__(545); +var Snapdragon = __webpack_require__(565); +var toRegex = __webpack_require__(522); /** * Parses the given POSIX character class `pattern` and returns a @@ -78541,13 +78457,13 @@ module.exports = brackets; /***/ }), -/* 658 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(659); +var posix = __webpack_require__(657); module.exports = function(brackets) { brackets.compiler @@ -78635,7 +78551,7 @@ module.exports = function(brackets) { /***/ }), -/* 659 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78664,14 +78580,14 @@ module.exports = { /***/ }), -/* 660 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(661); -var define = __webpack_require__(595); +var utils = __webpack_require__(659); +var define = __webpack_require__(593); /** * Text regex @@ -78890,14 +78806,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 661 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(524); -var regexNot = __webpack_require__(543); +var toRegex = __webpack_require__(522); +var regexNot = __webpack_require__(541); var cached; /** @@ -78931,15 +78847,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 662 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(657); -var define = __webpack_require__(663); -var utils = __webpack_require__(664); +var brackets = __webpack_require__(655); +var define = __webpack_require__(661); +var utils = __webpack_require__(662); /** * Characters to use in text regex (we want to "not" match @@ -79094,7 +79010,7 @@ module.exports = parsers; /***/ }), -/* 663 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79107,7 +79023,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(531); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -79132,14 +79048,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 664 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(543); -var Cache = __webpack_require__(648); +var regex = __webpack_require__(541); +var Cache = __webpack_require__(646); /** * Utils @@ -79208,7 +79124,7 @@ utils.createRegex = function(str) { /***/ }), -/* 665 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79218,16 +79134,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(567); -var define = __webpack_require__(663); -var extend = __webpack_require__(547); +var Snapdragon = __webpack_require__(565); +var define = __webpack_require__(661); +var extend = __webpack_require__(545); /** * Local dependencies */ -var compilers = __webpack_require__(656); -var parsers = __webpack_require__(662); +var compilers = __webpack_require__(654); +var parsers = __webpack_require__(660); /** * Customize Snapdragon parser and renderer @@ -79293,16 +79209,16 @@ module.exports = Extglob; /***/ }), -/* 666 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(655); -var nanomatch = __webpack_require__(640); -var regexNot = __webpack_require__(543); -var toRegex = __webpack_require__(524); +var extglob = __webpack_require__(653); +var nanomatch = __webpack_require__(638); +var regexNot = __webpack_require__(541); +var toRegex = __webpack_require__(522); var not; /** @@ -79383,14 +79299,14 @@ function textRegex(pattern) { /***/ }), -/* 667 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(648))(); +module.exports = new (__webpack_require__(646))(); /***/ }), -/* 668 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79403,13 +79319,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(567); -utils.define = __webpack_require__(669); -utils.diff = __webpack_require__(652); -utils.extend = __webpack_require__(637); -utils.pick = __webpack_require__(653); -utils.typeOf = __webpack_require__(670); -utils.unique = __webpack_require__(546); +var Snapdragon = __webpack_require__(565); +utils.define = __webpack_require__(667); +utils.diff = __webpack_require__(650); +utils.extend = __webpack_require__(635); +utils.pick = __webpack_require__(651); +utils.typeOf = __webpack_require__(668); +utils.unique = __webpack_require__(544); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -79706,7 +79622,7 @@ utils.unixify = function(options) { /***/ }), -/* 669 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79719,8 +79635,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(532); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(530); +var isDescriptor = __webpack_require__(531); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -79751,7 +79667,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 670 */ +/* 668 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -79886,7 +79802,7 @@ function isBuffer(val) { /***/ }), -/* 671 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79905,9 +79821,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(672); -var reader_1 = __webpack_require__(685); -var fs_stream_1 = __webpack_require__(689); +var readdir = __webpack_require__(670); +var reader_1 = __webpack_require__(683); +var fs_stream_1 = __webpack_require__(687); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -79968,15 +79884,15 @@ exports.default = ReaderAsync; /***/ }), -/* 672 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(673); -const readdirAsync = __webpack_require__(681); -const readdirStream = __webpack_require__(684); +const readdirSync = __webpack_require__(671); +const readdirAsync = __webpack_require__(679); +const readdirStream = __webpack_require__(682); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -80060,7 +79976,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 673 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80068,11 +79984,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(674); +const DirectoryReader = __webpack_require__(672); let syncFacade = { - fs: __webpack_require__(679), - forEach: __webpack_require__(680), + fs: __webpack_require__(677), + forEach: __webpack_require__(678), sync: true }; @@ -80101,7 +80017,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 674 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80110,9 +80026,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(675); -const stat = __webpack_require__(677); -const call = __webpack_require__(678); +const normalizeOptions = __webpack_require__(673); +const stat = __webpack_require__(675); +const call = __webpack_require__(676); /** * Asynchronously reads the contents of a directory and streams the results @@ -80488,14 +80404,14 @@ module.exports = DirectoryReader; /***/ }), -/* 675 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(676); +const globToRegExp = __webpack_require__(674); module.exports = normalizeOptions; @@ -80672,7 +80588,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 676 */ +/* 674 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -80809,13 +80725,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 677 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(678); +const call = __webpack_require__(676); module.exports = stat; @@ -80890,7 +80806,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 678 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80951,14 +80867,14 @@ function callOnce (fn) { /***/ }), -/* 679 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(678); +const call = __webpack_require__(676); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -81022,7 +80938,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 680 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81051,7 +80967,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 681 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81059,12 +80975,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(682); -const DirectoryReader = __webpack_require__(674); +const maybe = __webpack_require__(680); +const DirectoryReader = __webpack_require__(672); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(683), + forEach: __webpack_require__(681), async: true }; @@ -81106,7 +81022,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 682 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81133,7 +81049,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 683 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81169,7 +81085,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 684 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81177,11 +81093,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(674); +const DirectoryReader = __webpack_require__(672); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(683), + forEach: __webpack_require__(681), async: true }; @@ -81201,16 +81117,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 685 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(686); -var entry_1 = __webpack_require__(688); -var pathUtil = __webpack_require__(687); +var deep_1 = __webpack_require__(684); +var entry_1 = __webpack_require__(686); +var pathUtil = __webpack_require__(685); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -81276,14 +81192,14 @@ exports.default = Reader; /***/ }), -/* 686 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(687); -var patternUtils = __webpack_require__(518); +var pathUtils = __webpack_require__(685); +var patternUtils = __webpack_require__(516); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -81366,7 +81282,7 @@ exports.default = DeepFilter; /***/ }), -/* 687 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81397,14 +81313,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 688 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(687); -var patternUtils = __webpack_require__(518); +var pathUtils = __webpack_require__(685); +var patternUtils = __webpack_require__(516); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -81489,7 +81405,7 @@ exports.default = EntryFilter; /***/ }), -/* 689 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81509,8 +81425,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(690); -var fs_1 = __webpack_require__(694); +var fsStat = __webpack_require__(688); +var fs_1 = __webpack_require__(692); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -81560,14 +81476,14 @@ exports.default = FileSystemStream; /***/ }), -/* 690 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(691); -const statProvider = __webpack_require__(693); +const optionsManager = __webpack_require__(689); +const statProvider = __webpack_require__(691); /** * Asynchronous API. */ @@ -81598,13 +81514,13 @@ exports.statSync = statSync; /***/ }), -/* 691 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(692); +const fsAdapter = __webpack_require__(690); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -81617,7 +81533,7 @@ exports.prepare = prepare; /***/ }), -/* 692 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81640,7 +81556,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 693 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81692,7 +81608,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 694 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81723,7 +81639,7 @@ exports.default = FileSystem; /***/ }), -/* 695 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81743,9 +81659,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(672); -var reader_1 = __webpack_require__(685); -var fs_stream_1 = __webpack_require__(689); +var readdir = __webpack_require__(670); +var reader_1 = __webpack_require__(683); +var fs_stream_1 = __webpack_require__(687); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -81813,7 +81729,7 @@ exports.default = ReaderStream; /***/ }), -/* 696 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81832,9 +81748,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(672); -var reader_1 = __webpack_require__(685); -var fs_sync_1 = __webpack_require__(697); +var readdir = __webpack_require__(670); +var reader_1 = __webpack_require__(683); +var fs_sync_1 = __webpack_require__(695); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -81894,7 +81810,7 @@ exports.default = ReaderSync; /***/ }), -/* 697 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81913,8 +81829,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(690); -var fs_1 = __webpack_require__(694); +var fsStat = __webpack_require__(688); +var fs_1 = __webpack_require__(692); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -81960,7 +81876,7 @@ exports.default = FileSystemSync; /***/ }), -/* 698 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81976,7 +81892,7 @@ exports.flatten = flatten; /***/ }), -/* 699 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81997,13 +81913,13 @@ exports.merge = merge; /***/ }), -/* 700 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(701); +const pathType = __webpack_require__(699); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -82069,13 +81985,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 701 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(702); +const pify = __webpack_require__(700); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -82118,7 +82034,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 702 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82209,17 +82125,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 703 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(514); -const gitIgnore = __webpack_require__(704); -const pify = __webpack_require__(705); -const slash = __webpack_require__(706); +const fastGlob = __webpack_require__(512); +const gitIgnore = __webpack_require__(702); +const pify = __webpack_require__(703); +const slash = __webpack_require__(704); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -82317,7 +82233,7 @@ module.exports.sync = options => { /***/ }), -/* 704 */ +/* 702 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -82786,7 +82702,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 705 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82861,7 +82777,7 @@ module.exports = (input, options) => { /***/ }), -/* 706 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82879,7 +82795,7 @@ module.exports = input => { /***/ }), -/* 707 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82892,7 +82808,7 @@ module.exports = input => { -var isGlob = __webpack_require__(708); +var isGlob = __webpack_require__(706); module.exports = function hasGlob(val) { if (val == null) return false; @@ -82912,7 +82828,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 708 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -82943,17 +82859,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 709 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(710); -const CpFileError = __webpack_require__(713); -const fs = __webpack_require__(715); -const ProgressEmitter = __webpack_require__(718); +const pEvent = __webpack_require__(708); +const CpFileError = __webpack_require__(711); +const fs = __webpack_require__(713); +const ProgressEmitter = __webpack_require__(716); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -83067,12 +82983,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 710 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(711); +const pTimeout = __webpack_require__(709); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -83363,12 +83279,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 711 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(712); +const pFinally = __webpack_require__(710); class TimeoutError extends Error { constructor(message) { @@ -83414,7 +83330,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 712 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83436,12 +83352,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 713 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(714); +const NestedError = __webpack_require__(712); class CpFileError extends NestedError { constructor(message, nested) { @@ -83455,7 +83371,7 @@ module.exports = CpFileError; /***/ }), -/* 714 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -83511,16 +83427,16 @@ module.exports = NestedError; /***/ }), -/* 715 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(716); -const pEvent = __webpack_require__(710); -const CpFileError = __webpack_require__(713); +const makeDir = __webpack_require__(714); +const pEvent = __webpack_require__(708); +const CpFileError = __webpack_require__(711); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -83617,7 +83533,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 716 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83625,7 +83541,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(717); +const semver = __webpack_require__(715); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -83780,7 +83696,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 717 */ +/* 715 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -85382,7 +85298,7 @@ function coerce (version, options) { /***/ }), -/* 718 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85423,7 +85339,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 719 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85469,12 +85385,12 @@ exports.default = module.exports; /***/ }), -/* 720 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(721); +const pMap = __webpack_require__(719); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -85491,7 +85407,7 @@ module.exports.default = pFilter; /***/ }), -/* 721 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85570,12 +85486,12 @@ module.exports.default = pMap; /***/ }), -/* 722 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(714); +const NestedError = __webpack_require__(712); class CpyError extends NestedError { constructor(message, nested) { diff --git a/packages/kbn-ui-framework/generator-kui/app/component.js b/packages/kbn-ui-framework/generator-kui/app/component.js deleted file mode 100644 index bcb561f6fa7295..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/app/component.js +++ /dev/null @@ -1,52 +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. - */ - -const Generator = require('yeoman-generator'); - -const componentGenerator = require.resolve('../component/index.js'); - -module.exports = class extends Generator { - prompting() { - return this.prompt([ - { - message: 'What do you want to create?', - name: 'fileType', - type: 'list', - choices: [ - { - name: 'Stateless function', - value: 'function', - }, - { - name: 'Component class', - value: 'component', - }, - ], - }, - ]).then((answers) => { - this.config = answers; - }); - } - - writing() { - this.composeWith(componentGenerator, { - fileType: this.config.fileType, - }); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/app/documentation.js b/packages/kbn-ui-framework/generator-kui/app/documentation.js deleted file mode 100644 index 3cbc0263789c65..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/app/documentation.js +++ /dev/null @@ -1,56 +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. - */ - -const Generator = require('yeoman-generator'); - -const documentationGenerator = require.resolve('../documentation/index.js'); - -module.exports = class extends Generator { - prompting() { - return this.prompt([ - { - message: 'What do you want to create?', - name: 'fileType', - type: 'list', - choices: [ - { - name: 'Page', - value: 'documentation', - }, - { - name: 'Page demo', - value: 'demo', - }, - { - name: 'Sandbox', - value: 'sandbox', - }, - ], - }, - ]).then((answers) => { - this.config = answers; - }); - } - - writing() { - this.composeWith(documentationGenerator, { - fileType: this.config.fileType, - }); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/component/index.js b/packages/kbn-ui-framework/generator-kui/component/index.js deleted file mode 100644 index 56c49fe6fa4717..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/index.js +++ /dev/null @@ -1,156 +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. - */ - -const chalk = require('chalk'); -const { resolve } = require('path'); -const Generator = require('yeoman-generator'); -const utils = require('../utils'); - -module.exports = class extends Generator { - constructor(args, options) { - super(args, options); - - this.fileType = options.fileType; - } - - prompting() { - return this.prompt([ - { - message: "What's the name of this component? Use snake_case, please.", - name: 'name', - type: 'input', - }, - { - message: `Where do you want to create this component's files?`, - type: 'input', - name: 'path', - default: resolve(__dirname, '../../src/components'), - store: true, - }, - { - message: 'Does it need its own directory?', - name: 'shouldMakeDirectory', - type: 'confirm', - default: true, - }, - ]).then((answers) => { - this.config = answers; - - if (!answers.name || !answers.name.trim()) { - this.log.error('Sorry, please run this generator again and provide a component name.'); - process.exit(1); - } - }); - } - - writing() { - const config = this.config; - - const writeComponent = (isStatelessFunction) => { - const componentName = utils.makeComponentName(config.name); - const cssClassName = utils.lowerCaseFirstLetter(componentName); - const fileName = config.name; - - const path = utils.addDirectoryToPath(config.path, fileName, config.shouldMakeDirectory); - - const vars = (config.vars = { - componentName, - cssClassName, - fileName, - }); - - const componentPath = (config.componentPath = `${path}/${fileName}.js`); - const testPath = (config.testPath = `${path}/${fileName}.test.js`); - const stylesPath = (config.stylesPath = `${path}/_${fileName}.scss`); - config.stylesImportPath = `./_${fileName}.scss`; - - // If it needs its own directory then it will need a root index file too. - if (this.config.shouldMakeDirectory) { - this.fs.copyTpl( - this.templatePath('_index.scss'), - this.destinationPath(`${path}/_index.scss`), - vars - ); - - this.fs.copyTpl( - this.templatePath('index.js'), - this.destinationPath(`${path}/index.js`), - vars - ); - } - - // Create component file. - this.fs.copyTpl( - isStatelessFunction - ? this.templatePath('stateless_function.js') - : this.templatePath('component.js'), - this.destinationPath(componentPath), - vars - ); - - // Create component test file. - this.fs.copyTpl(this.templatePath('test.js'), this.destinationPath(testPath), vars); - - // Create component styles file. - this.fs.copyTpl(this.templatePath('_component.scss'), this.destinationPath(stylesPath), vars); - }; - - switch (this.fileType) { - case 'component': - writeComponent(); - break; - - case 'function': - writeComponent(true); - break; - } - } - - end() { - const showImportComponentSnippet = () => { - const componentName = this.config.vars.componentName; - - this.log(chalk.white(`\n// Export component (e.. from component's index.js).`)); - this.log( - `${chalk.magenta('export')} {\n` + - ` ${componentName},\n` + - `} ${chalk.magenta('from')} ${chalk.cyan(`'./${this.config.name}'`)};` - ); - - this.log(chalk.white('\n// Import styles.')); - this.log(`${chalk.magenta('@import')} ${chalk.cyan(`'${this.config.name}'`)};`); - - this.log(chalk.white('\n// Import component styles into the root index.scss.')); - this.log(`${chalk.magenta('@import')} ${chalk.cyan(`'${this.config.name}/index'`)};`); - }; - - this.log('------------------------------------------------'); - this.log(chalk.bold('Handy snippets:')); - switch (this.fileType) { - case 'component': - showImportComponentSnippet(); - break; - - case 'function': - showImportComponentSnippet(); - break; - } - this.log('------------------------------------------------'); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss b/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss deleted file mode 100644 index 668cabce61327d..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.<%= cssClassName %> { - -} diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss b/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss deleted file mode 100644 index 088dee98749468..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import '<%= fileName %>'; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/component.js b/packages/kbn-ui-framework/generator-kui/component/templates/component.js deleted file mode 100644 index 31e362222471ad..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/component.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { - Component, -} from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -export class <%= componentName %> extends Component { - static propTypes = { - children: PropTypes.node, - className: PropTypes.string, - } - - constructor(props) { - super(props); - } - - render() { - const { - children, - className, - ...rest - } = this.props; - - const classes = classNames('<%= cssClassName %>', className); - - return ( -
- {children} -
- ); - } -} diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/index.js b/packages/kbn-ui-framework/generator-kui/component/templates/index.js deleted file mode 100644 index 1da6deaa79d9ae..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { - <%= componentName %>, -} from './<%= fileName %>'; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js b/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js deleted file mode 100644 index 7fcbf0c19d728e..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -export const <%= componentName %> = ({ - children, - className, - ...rest -}) => { - const classes = classNames('<%= cssClassName %>', className); - - return ( -
- {children} -
- ); -}; - -<%= componentName %>.propTypes = { - children: PropTypes.node, - className: PropTypes.string, -}; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/test.js b/packages/kbn-ui-framework/generator-kui/component/templates/test.js deleted file mode 100644 index 4f384d6c2d3aa0..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/test.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { render } from 'enzyme'; -import { requiredProps } from '../../test/required_props'; - -import { <%= componentName %> } from './<%= fileName %>'; - -describe('<%= componentName %>', () => { - test('is rendered', () => { - const component = render( - <<%= componentName %> {...requiredProps} /> - ); - - expect(component) - .toMatchSnapshot(); - }); -}); diff --git a/packages/kbn-ui-framework/generator-kui/documentation/index.js b/packages/kbn-ui-framework/generator-kui/documentation/index.js deleted file mode 100644 index 03f8d5813b2515..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/index.js +++ /dev/null @@ -1,238 +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. - */ - -const chalk = require('chalk'); -const { resolve } = require('path'); -const Generator = require('yeoman-generator'); -const utils = require('../utils'); - -const DOCUMENTATION_PAGE_PATH = resolve(__dirname, '../../doc_site/src/views'); - -module.exports = class extends Generator { - constructor(args, options) { - super(args, options); - - this.fileType = options.fileType; - } - - prompting() { - const prompts = [ - { - message: "What's the name of the component you're documenting? Use snake_case, please.", - name: 'name', - type: 'input', - store: true, - }, - ]; - - if (this.fileType === 'demo') { - prompts.push({ - message: `What's the name of the directory this demo should go in? (Within ${DOCUMENTATION_PAGE_PATH}). Use snake_case, please.`, - name: 'folderName', - type: 'input', - store: true, - default: (answers) => answers.name, - }); - - prompts.push({ - message: 'What would you like to name this demo? Use snake_case, please.', - name: 'demoName', - type: 'input', - store: true, - }); - } - - return this.prompt(prompts).then((answers) => { - this.config = answers; - }); - } - - writing() { - const config = this.config; - - const writeDocumentationPage = () => { - const componentExampleName = utils.makeComponentName(config.name, false); - const componentExamplePrefix = utils.lowerCaseFirstLetter(componentExampleName); - const fileName = config.name; - - const path = DOCUMENTATION_PAGE_PATH; - - const vars = (config.documentationVars = { - componentExampleName, - componentExamplePrefix, - fileName, - }); - - const documentationPagePath = (config.documentationPagePath = `${path}/${config.name}/${config.name}_example.js`); - - this.fs.copyTpl( - this.templatePath('documentation_page.js'), - this.destinationPath(documentationPagePath), - vars - ); - }; - - const writeDocumentationPageDemo = (fileName, folderName) => { - const componentExampleName = utils.makeComponentName(fileName, false); - const componentExamplePrefix = utils.lowerCaseFirstLetter(componentExampleName); - const componentName = utils.makeComponentName(config.name); - - const path = DOCUMENTATION_PAGE_PATH; - - const vars = (config.documentationVars = { - componentExampleName, - componentExamplePrefix, - componentName, - fileName, - }); - - const documentationPageDemoPath = (config.documentationPageDemoPath = `${path}/${folderName}/${fileName}.js`); - - this.fs.copyTpl( - this.templatePath('documentation_page_demo.js'), - this.destinationPath(documentationPageDemoPath), - vars - ); - }; - - const writeSandbox = () => { - const fileName = config.name; - const componentExampleName = utils.makeComponentName(fileName, false); - - const path = DOCUMENTATION_PAGE_PATH; - - const vars = (config.documentationVars = { - componentExampleName, - fileName, - }); - - const sandboxPath = (config.documentationPageDemoPath = `${path}/${config.name}/${fileName}`); - - this.fs.copyTpl( - this.templatePath('documentation_sandbox.html'), - this.destinationPath(`${sandboxPath}_sandbox.html`) - ); - - this.fs.copyTpl( - this.templatePath('documentation_sandbox.js'), - this.destinationPath(`${sandboxPath}_sandbox.js`), - vars - ); - }; - - switch (this.fileType) { - case 'documentation': - writeDocumentationPage(); - writeDocumentationPageDemo(config.name, config.name); - break; - - case 'demo': - writeDocumentationPageDemo(config.demoName, config.folderName); - break; - - case 'sandbox': - writeSandbox(); - break; - } - } - - end() { - const showImportDemoSnippet = () => { - const { - componentExampleName, - componentExamplePrefix, - fileName, - } = this.config.documentationVars; - - this.log(chalk.white('\n// Import demo into example.')); - this.log( - `${chalk.magenta('import')} ${componentExampleName} from ${chalk.cyan( - `'./${fileName}'` - )};\n` + - `${chalk.magenta('const')} ${componentExamplePrefix}Source = require(${chalk.cyan( - `'!!raw-loader!./${fileName}'` - )});\n` + - `${chalk.magenta( - 'const' - )} ${componentExamplePrefix}Html = renderToHtml(${componentExampleName});` - ); - - this.log(chalk.white('\n// Render demo.')); - this.log( - `\n` + - ` \n` + - ` Description needed: how to use the ${componentExampleName} component.\n` + - ` \n` + - `\n` + - ` \n` + - ` <${componentExampleName} />\n` + - ` \n` + - `\n` - ); - }; - - const showImportRouteSnippet = (suffix, appendToRoute) => { - const { componentExampleName, fileName } = this.config.documentationVars; - - this.log(chalk.white('\n// Import example into routes.js.')); - this.log( - `${chalk.magenta('import')} ${componentExampleName}${suffix}\n` + - ` ${chalk.magenta('from')} ${chalk.cyan( - `'../../views/${fileName}/${fileName}_${suffix.toLowerCase()}'` - )};` - ); - - this.log(chalk.white('\n// Import route definition into routes.js.')); - this.log( - `{\n` + - ` name: ${chalk.cyan(`'${componentExampleName}${appendToRoute ? suffix : ''}'`)},\n` + - ` component: ${componentExampleName}${suffix},\n` + - ` hasReact: ${chalk.magenta('true')},\n` + - `}` - ); - }; - - this.log('------------------------------------------------'); - this.log(chalk.bold('Import snippets:')); - - switch (this.fileType) { - case 'documentation': - showImportRouteSnippet('Example'); - break; - - case 'demo': - showImportDemoSnippet(); - break; - - case 'sandbox': - showImportRouteSnippet('Sandbox', true); - break; - } - this.log('------------------------------------------------'); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js deleted file mode 100644 index df45099bb9c647..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable import/no-duplicates */ - -import React from 'react'; - -import { renderToHtml } from '../../services'; - -import { - GuideCode, - GuideDemo, - GuidePage, - GuideSection, - GuideSectionTypes, - GuideText, -} from '../../components'; - -import <%= componentExampleName %> from './<%= fileName %>'; -import <%= componentExamplePrefix %>Source from '!!raw-loader!./<%= fileName %>'; // eslint-disable-line import/default -const <%= componentExamplePrefix %>Html = renderToHtml(<%= componentExampleName %>); - -export default props => ( - - Source, - }, { - type: GuideSectionTypes.HTML, - code: <%= componentExamplePrefix %>Html, - }]} - > - - Description needed: how to use the <%= componentExampleName %> component. - - - - <<%= componentExampleName %> /> - - - -); diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js deleted file mode 100644 index 645f194bb3c7b4..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import { - <%= componentName %>, -} from '../../../../components'; - -export default () => ( - <<%= componentName %>> - - > -); diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html deleted file mode 100644 index 2515d47beb72f9..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html +++ /dev/null @@ -1 +0,0 @@ -

Do whatever you want here!

diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js deleted file mode 100644 index 6dd661601b891d..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import { - GuideDemo, - GuideSandbox, - GuideSandboxCodeToggle, - GuideSectionTypes, -} from '../../components'; - -import html from './<%= fileName %>_sandbox.html'; - -export default props => ( - - - - - -); diff --git a/packages/kbn-ui-framework/generator-kui/utils.js b/packages/kbn-ui-framework/generator-kui/utils.js deleted file mode 100644 index 0f7b910451767e..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/utils.js +++ /dev/null @@ -1,56 +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. - */ - -function makeComponentName(str, usePrefix = true) { - const words = str.split('_'); - - const componentName = words - .map(function (word) { - return upperCaseFirstLetter(word); - }) - .join(''); - - return `${usePrefix ? 'Kui' : ''}${componentName}`; -} - -function lowerCaseFirstLetter(str) { - return str.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toLowerCase() + txt.substr(1); - }); -} - -function upperCaseFirstLetter(str) { - return str.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1); - }); -} - -function addDirectoryToPath(path, dirName, shouldMakeDirectory) { - if (shouldMakeDirectory) { - return path + '/' + dirName; - } - return path; -} - -module.exports = { - makeComponentName: makeComponentName, - lowerCaseFirstLetter: lowerCaseFirstLetter, - upperCaseFirstLetter: upperCaseFirstLetter, - addDirectoryToPath: addDirectoryToPath, -}; diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index 9e3adf3f61f81b..2b66f684b0c5d3 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -5,9 +5,7 @@ "scripts": { "build": "../../node_modules/.bin/grunt prodBuild", "docSiteStart": "../../node_modules/.bin/grunt docSiteStart", - "docSiteBuild": "../../node_modules/.bin/grunt docSiteBuild", - "createComponent": "../../node_modules/.bin/yo ./generator-kui/app/component.js", - "documentComponent": "../../node_modules/.bin/yo ./generator-kui/app/documentation.js" + "docSiteBuild": "../../node_modules/.bin/grunt docSiteBuild" }, "kibana": { "build": { diff --git a/yarn.lock b/yarn.lock index c3d36f42d31807..833e8bffcfc806 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3399,11 +3399,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - "@sindresorhus/is@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f" @@ -6461,14 +6456,6 @@ aggregate-error@2.1.0: clean-stack "^2.0.0" indent-string "^3.0.0" -aggregate-error@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-1.0.0.tgz#888344dad0220a72e3af50906117f48771925fac" - integrity sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w= - dependencies: - clean-stack "^1.0.0" - indent-string "^3.0.0" - aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" @@ -6620,13 +6607,6 @@ angular@>=1.0.6, angular@^1.8.0: resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.0.tgz#b1ec179887869215cab6dfd0df2e42caa65b1b51" integrity sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg== -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -6717,7 +6697,7 @@ ansi-styles@^2.0.1, ansi-styles@^2.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.0.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -6749,11 +6729,6 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= -ansi@^0.3.0, ansi@~0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21" - integrity sha1-DELU+xcWDVqa8eSEus4cZpIsGyE= - ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" @@ -7154,11 +7129,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= - array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" @@ -7259,7 +7229,7 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-uniq@^1.0.0, array-uniq@^1.0.1: +array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= @@ -7486,7 +7456,7 @@ async@^1.4.2, async@^1.5.2, async@~1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.0.0, async@^2.1.4, async@^2.6.0, async@^2.6.1, async@^2.6.2, async@^2.6.3: +async@^2.1.4, async@^2.6.0, async@^2.6.1, async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -8228,23 +8198,6 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bin-version-check@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-3.0.0.tgz#e24ebfa6b63cb0387c5fc174f86e5cc812ca7cc9" - integrity sha1-4k6/prY8sDh8X8F0+G5cyBLKfMk= - dependencies: - bin-version "^2.0.0" - semver "^5.1.0" - semver-truncate "^1.0.0" - -bin-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-2.0.0.tgz#2cc95d83b522bdef2e99978e76aeb5491c8114ff" - integrity sha1-LMldg7Uive8umZeOdq61SRyBFP8= - dependencies: - execa "^0.1.1" - find-versions "^2.0.0" - binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" @@ -8260,11 +8213,6 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== -binaryextensions@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935" - integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA== - bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -8391,19 +8339,6 @@ bowser@^1.7.3: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== -boxen@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== - dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" - boxen@^4.1.0, boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -8930,19 +8865,6 @@ cacheable-lookup@^2.0.0: "@types/keyv" "^3.1.1" keyv "^4.0.0" -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -9083,11 +9005,6 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -9292,11 +9209,6 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.1.tgz#942835f750e4ec61a308e60c2ef8cc1011202efc" integrity sha1-lCg191Dk7GGjCOYMLvjMEBEgLvw= -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -9466,11 +9378,6 @@ chromedriver@^86.0.0: mkdirp "^1.0.4" tcp-port-used "^1.0.1" -ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" - integrity sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -9489,13 +9396,6 @@ circular-json@^0.3.1: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== -class-extend@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/class-extend/-/class-extend-0.1.2.tgz#8057a82b00f53f82a5d62c50ef8cffdec6fabc34" - integrity sha1-gFeoKwD1P4Kl1ixQ74z/3sb6vDQ= - dependencies: - object-assign "^2.0.0" - class-utils@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" @@ -9519,7 +9419,7 @@ clean-css@4.2.x, clean-css@^4.2.3: dependencies: source-map "~0.6.0" -clean-stack@^1.0.0, clean-stack@^1.3.0: +clean-stack@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31" integrity sha1-noIVAa6XmYbEax1m0tQy2y/UrjE= @@ -9537,11 +9437,6 @@ clean-webpack-plugin@^3.0.0: "@types/webpack" "^4.4.31" del "^4.1.1" -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= - cli-boxes@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" @@ -9568,11 +9463,6 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-list@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/cli-list/-/cli-list-0.2.0.tgz#7e673ee0dd39a611a486476e53f3c6b3941cb582" - integrity sha1-fmc+4N05phGkhkduU/PGs5QctYI= - cli-spinners@^2.0.0, cli-spinners@^2.2.0, cli-spinners@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" @@ -9719,32 +9609,19 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-1.0.0.tgz#eae0a2413f55c0942f818c229fefce845d7f3b1c" - integrity sha1-6uCiQT9VwJQvgYwin+/OhF1/Oxw= - dependencies: - is-regexp "^1.0.0" - is-supported-regexp-flag "^1.0.0" - -clone-response@1.0.2, clone-response@^1.0.2: +clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= dependencies: mimic-response "^1.0.0" -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= - clone-stats@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^1.0.0, clone@^1.0.2, clone@^1.0.4: +clone@^1.0.2, clone@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= @@ -10093,18 +9970,7 @@ concat-stream@~2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -conf@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/conf/-/conf-1.4.0.tgz#1ea66c9d7a9b601674a5bb9d2b8dc3c726625e67" - integrity sha512-bzlVWS2THbMetHqXKB8ypsXN4DQ/1qopGwNJi1eYbpwesJcd86FBjFciCQX/YwAhp9bM7NVnPFqZ5LpV7gP0Dg== - dependencies: - dot-prop "^4.1.0" - env-paths "^1.0.0" - make-dir "^1.0.0" - pkg-up "^2.0.0" - write-file-atomic "^2.3.0" - -config-chain@^1.1.11, config-chain@^1.1.12, config-chain@~1.1.8: +config-chain@^1.1.12, config-chain@~1.1.8: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -10126,18 +9992,6 @@ configstore@^1.0.0: write-file-atomic "^1.1.2" xdg-basedir "^2.0.0" -configstore@^3.0.0, configstore@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== - dependencies: - dot-prop "^4.1.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -10445,7 +10299,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.0, create-error-class@^3.0.1: +create-error-class@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= @@ -10516,14 +10370,6 @@ cross-fetch@2.2.2: node-fetch "2.1.2" whatwg-fetch "2.0.4" -cross-spawn-async@^2.1.1: - version "2.2.5" - resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" - integrity sha1-hF/wwINKPe2dFg2sptOQkGuyiMw= - dependencies: - lru-cache "^4.0.0" - which "^1.2.8" - cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -10552,15 +10398,6 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -11221,11 +11058,6 @@ damerau-levenshtein@^1.0.4: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" integrity sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ= -dargs@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-5.1.0.tgz#ec7ea50c78564cd36c9d5ec18f66329fade27829" - integrity sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk= - dash-ast@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" @@ -11279,11 +11111,6 @@ dateformat@^1.0.11, dateformat@~1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - dateformat@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -11298,7 +11125,7 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -11361,7 +11188,7 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -decompress-response@^3.2.0, decompress-response@^3.3.0: +decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= @@ -11438,11 +11265,6 @@ deep-equal@~1.0.1: resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= -deep-extend@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" - integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8= - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -11495,11 +11317,6 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -default-uid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-uid/-/default-uid-1.0.0.tgz#fcefa9df9f5ac40c8916d912dd1fe1146aa3c59e" - integrity sha1-/O+p359axAyJFtkS3R/hFGqjxZ4= - defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -11703,11 +11520,6 @@ detab@2.0.3, detab@^2.0.0: dependencies: repeat-string "^1.5.4" -detect-conflict@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e" - integrity sha1-CIZXpmqWHAUBnbfEIwiDsca0F24= - detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" @@ -11898,11 +11710,6 @@ diff@^1.3.2: resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= -diff@^2.1.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" - integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k= - diff@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" @@ -12103,13 +11910,6 @@ dot-case@^3.0.3: no-case "^3.0.3" tslib "^1.10.0" -dot-prop@^4.1.0, dot-prop@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" - integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== - dependencies: - is-obj "^1.0.0" - dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -12153,14 +11953,6 @@ dotignore@^0.1.2: dependencies: minimatch "^3.0.4" -downgrade-root@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/downgrade-root/-/downgrade-root-1.2.2.tgz#531319715b0e81ffcc22eb28478ba27643e12c6c" - integrity sha1-UxMZcVsOgf/MIusoR4uidkPhLGw= - dependencies: - default-uid "^1.0.0" - is-root "^1.0.0" - dtrace-provider@~0.8: version "0.8.8" resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" @@ -12203,14 +11995,6 @@ duration@^0.2.0: d "1" es5-ext "~0.10.46" -each-async@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473" - integrity sha1-3uUim98KtrogEqOV4bhpq/iBNHM= - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" - each-props@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" @@ -12275,11 +12059,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -ejs@^2.3.1: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== - ejs@^3.0.1, ejs@^3.1.2, ejs@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.5.tgz#aed723844dc20acb4b170cd9ab1017e476a0d93b" @@ -12445,7 +12224,7 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== -encodeurl@^1.0.2, encodeurl@~1.0.2: +encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= @@ -12503,11 +12282,6 @@ entities@~2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== -env-paths@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" - integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= - env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -12629,7 +12403,7 @@ error-stack-parser@^2.0.4, error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -error@^7.0.0, error@^7.0.2: +error@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" integrity sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI= @@ -13336,53 +13110,6 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== -execa@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.1.1.tgz#b09c2a9309bc0ef0501479472db3180f8d4c3edd" - integrity sha1-sJwqkwm8DvBQFHlHLbMYD41MPt0= - dependencies: - cross-spawn-async "^2.1.1" - object-assign "^4.0.1" - strip-eof "^1.0.0" - -execa@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.4.0.tgz#4eb6467a36a095fabb2970ff9d5e3fb7bce6ebc3" - integrity sha1-TrZGejaglfq7KXD/nV4/t7zm68M= - dependencies: - cross-spawn-async "^2.1.1" - is-stream "^1.1.0" - npm-run-path "^1.0.0" - object-assign "^4.0.1" - path-key "^1.0.0" - strip-eof "^1.0.0" - -execa@^0.6.0: - version "0.6.3" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe" - integrity sha1-V7aaWU8IF1nGnlNw8NF7nLEWWP4= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -13411,13 +13138,6 @@ execa@^4.0.0, execa@^4.0.2: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execall@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73" - integrity sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M= - dependencies: - clone-regexp "^1.0.0" - executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -13555,24 +13275,6 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" - integrity sha1-Etew24UPf/fnCBuvQAVwAGDEYAs= - dependencies: - extend "^3.0.0" - spawn-sync "^1.0.15" - tmp "^0.0.29" - -external-editor@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -13966,11 +13668,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= - finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -14045,14 +13742,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-2.0.0.tgz#2ad90d490f6828c1aa40292cf709ac3318210c3c" - integrity sha1-KtkNSQ9oKMGqQCks9wmsMxghDDw= - dependencies: - array-uniq "^1.0.0" - semver-regex "^1.0.0" - find@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find/-/find-0.3.0.tgz#4082e8fc8d8320f1a382b5e4f521b9bc50775cb8" @@ -14098,13 +13787,6 @@ fined@^1.0.1: object.pick "^1.2.0" parse-filepath "^1.0.1" -first-chunk-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70" - integrity sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA= - dependencies: - readable-stream "^2.0.2" - flagged-respawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" @@ -14403,7 +14085,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.1.0, from2@^2.1.1: +from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -14556,19 +14238,6 @@ fsu@^1.0.2: resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834" integrity sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A== -fullname@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/fullname/-/fullname-3.3.0.tgz#a08747d6921229610b8178b7614fce10cb185f5a" - integrity sha1-oIdH1pISKWELgXi3YU/OEMsYX1o= - dependencies: - execa "^0.6.0" - filter-obj "^1.1.0" - mem "^1.1.0" - p-any "^1.0.0" - p-try "^1.0.0" - passwd-user "^2.1.0" - rc "^1.1.6" - function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -14598,17 +14267,6 @@ fuse.js@^3.6.1: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c" integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw== -gauge@~1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93" - integrity sha1-6c7FSD09TuDvRLYKfZnkk14TbZM= - dependencies: - ansi "^0.3.0" - has-unicode "^2.0.0" - lodash.pad "^4.1.0" - lodash.padend "^4.1.0" - lodash.padstart "^4.1.0" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -14731,11 +14389,6 @@ get-stdin@^6.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -14779,14 +14432,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gh-got@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-5.0.0.tgz#ee95be37106fd8748a96f8d1db4baea89e1bfa8a" - integrity sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo= - dependencies: - got "^6.2.0" - is-plain-obj "^1.1.0" - gherkin@^5.0.0, gherkin@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5" @@ -14827,13 +14472,6 @@ github-slugger@^1.0.0: dependencies: emoji-regex ">=6.0.0 <=6.1.1" -github-username@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/github-username/-/github-username-3.0.0.tgz#0a772219b3130743429f2456d0bdd3db55dce7b1" - integrity sha1-CnciGbMTB0NCnyRW0L3T21Xc57E= - dependencies: - gh-got "^5.0.0" - gl-matrix@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b" @@ -14992,13 +14630,6 @@ glob@~7.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= - dependencies: - ini "^1.3.4" - global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -15042,16 +14673,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global-tunnel-ng@^2.5.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" - integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== - dependencies: - encodeurl "^1.0.2" - lodash "^4.17.10" - npm-conf "^1.1.3" - tunnel "^0.0.6" - global@^4.3.1, global@^4.3.2, global@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" @@ -15131,18 +14752,6 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8" - integrity sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg= - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^6.0.1" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -15276,66 +14885,6 @@ got@^3.2.0: read-all-stream "^3.0.0" timed-out "^2.0.0" -got@^6.2.0, got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= - dependencies: - create-error-class "^3.0.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - -got@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" - integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== - dependencies: - decompress-response "^3.2.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" - url-to-options "^1.0.1" - -got@^8.3.1, got@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -15602,13 +15151,6 @@ grid-index@^1.1.0: resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== -grouped-queue@^0.3.0, grouped-queue@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c" - integrity sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw= - dependencies: - lodash "^4.17.2" - growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -15894,11 +15436,6 @@ has-color@~0.1.0: resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8= -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -15921,23 +15458,11 @@ has-glob@^1.0.0: dependencies: is-glob "^3.0.0" -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - has-symbols@^1.0.0, has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -16385,11 +15910,6 @@ htmlparser2@~3.3.0: domutils "1.1" readable-stream "1.0" -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -16518,13 +16038,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -humanize-string@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/humanize-string/-/humanize-string-1.0.2.tgz#fef0a8bc9b1b857ca4013bbfaea75071736988f6" - integrity sha512-PH5GBkXqFxw5+4eKaKRIkD23y6vRd/IXSl7IldyJxEXpDH9SEIXRORkBtkGni/ae2P7RVOw6Wxypd2tGXhha1w== - dependencies: - decamelize "^1.0.0" - hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" @@ -16547,7 +16060,7 @@ icalendar@0.7.1: resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4= -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -16803,7 +16316,7 @@ inline-style@^2.0.0: dependencies: dashify "^0.1.0" -inquirer@6.2.2, inquirer@^6.0.0: +inquirer@6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== @@ -16860,45 +16373,6 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -inquirer@^1.0.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918" - integrity sha1-TexvMvN+97sLLtPx0aXD9UUHSRg= - dependencies: - ansi-escapes "^1.1.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - external-editor "^1.1.0" - figures "^1.3.5" - lodash "^4.3.0" - mute-stream "0.0.6" - pinkie-promise "^2.0.0" - run-async "^2.2.0" - rx "^4.1.0" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - -inquirer@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726" - integrity sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.1.0" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^5.5.2" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - inquirer@^7.0.0, inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -16934,21 +16408,6 @@ insert-module-globals@^7.0.0: undeclared-identifiers "^1.1.2" xtend "^4.0.0" -insight@0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/insight/-/insight-0.10.1.tgz#a0ecf668484a95d66e9be59644964e719cc83380" - integrity sha512-kLGeYQkh18f8KuC68QKdi0iwUcIaayJVB/STpX7x452/7pAUm1yfG4giJwcxbrTh0zNYtc8kBR+6maLMOzglOQ== - dependencies: - async "^2.1.4" - chalk "^2.3.0" - conf "^1.3.1" - inquirer "^5.0.0" - lodash.debounce "^4.0.8" - os-name "^2.0.1" - request "^2.74.0" - tough-cookie "^2.0.0" - uuid "^3.0.0" - install-artifact-from-github@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.0.2.tgz#e1e478dd29880b9112ecd684a84029603e234a9d" @@ -17010,14 +16469,6 @@ intl@^1.2.5: resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" integrity sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94= -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - invariant@2.2.4, invariant@^2.1.0, invariant@^2.1.1, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -17169,13 +16620,6 @@ is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" - integrity sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg== - dependencies: - ci-info "^1.0.0" - is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -17230,11 +16674,6 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-docker@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-1.1.0.tgz#f04374d4eee5310e9a8e113bf1495411e46176a1" - integrity sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE= - is-docker@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" @@ -17347,14 +16786,6 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" integrity sha1-bghLvJIGH7sJcexYts5tQE4k2mk= -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - is-installed-globally@^0.3.1, is-installed-globally@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" @@ -17465,7 +16896,7 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0, is-obj@^1.0.1: +is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= @@ -17615,7 +17046,7 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: +is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= @@ -17625,18 +17056,6 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-root@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" - integrity sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU= - -is-scoped@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-scoped/-/is-scoped-1.0.0.tgz#449ca98299e713038256289ecb2b540dc437cb30" - integrity sha1-RJypgpnnEwOCViieyytUDcQ3yzA= - dependencies: - scoped-regex "^1.0.0" - is-secret@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/is-secret/-/is-secret-1.2.1.tgz#04b9ca1880ea763049606cfe6c2a08a93f33abe3" @@ -17674,11 +17093,6 @@ is-subset@^0.1.1: resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= -is-supported-regexp-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz#8b520c85fae7a253382d4b02652e045576e13bb8" - integrity sha1-i1IMhfrnolM4LUsCZS4EVXbhO7g= - is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -17949,23 +17363,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -istextorbinary@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53" - integrity sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw== - dependencies: - binaryextensions "2" - editions "^1.3.3" - textextensions "2" - -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - iterall@^1.1.3, iterall@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" @@ -19057,7 +18454,7 @@ keymirror@0.1.1: resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" integrity sha1-kYiJ6hP40KQufFVyUO7nE63JXDU= -keyv@3.0.0, keyv@^3.0.0: +keyv@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== @@ -19173,13 +18570,6 @@ latest-version@^1.0.0: dependencies: package-json "^1.0.0" -latest-version@^3.0.0, latest-version@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= - dependencies: - package-json "^4.0.0" - latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -19484,16 +18874,6 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - load-json-file@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" @@ -19566,11 +18946,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -locutus@^2.0.5: - version "2.0.10" - resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917" - integrity sha512-AZg2kCqrquMJ5FehDsBidV0qHl98NrsYtseUClzjAQ3HFnsDBJTCwGVplSQ82t9/QfgahqvTjaKcZqZkHmS0wQ== - lodash-es@^4.17.11: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" @@ -19756,21 +19131,6 @@ lodash.once@^4.0.0, lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.pad@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70" - integrity sha1-QzCUmoM6fI2iLMIPaibE1Z3runA= - -lodash.padend@^4.1.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" - integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= - -lodash.padstart@^4.1.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b" - integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs= - lodash.partialright@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partialright/-/lodash.partialright-4.2.1.tgz#0130d80e83363264d40074f329b8a3e7a8a1cc4b" @@ -19841,7 +19201,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -19868,7 +19228,7 @@ log-symbols@3.0.0, log-symbols@^3.0.0: dependencies: chalk "^2.4.2" -log-symbols@^1.0.1, log-symbols@^1.0.2: +log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg= @@ -19971,11 +19331,6 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -20035,11 +19390,6 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -macos-release@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-1.1.0.tgz#831945e29365b470aa8724b0ab36c8f8959d10fb" - integrity sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA== - macos-release@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.2.0.tgz#ab58d55dd4714f0a05ad4b0e90f4370fef5cdea8" @@ -20070,13 +19420,6 @@ magic-string@0.25.1: dependencies: sourcemap-codec "^1.4.1" -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -20346,38 +19689,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem-fs-editor@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz#dd0a6eaf2bb8a6b37740067aa549eb530105af9f" - integrity sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58= - dependencies: - commondir "^1.0.1" - deep-extend "^0.4.0" - ejs "^2.3.1" - glob "^7.0.3" - globby "^6.1.0" - mkdirp "^0.5.0" - multimatch "^2.0.0" - rimraf "^2.2.8" - through2 "^2.0.0" - vinyl "^2.0.1" - -mem-fs@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.1.3.tgz#b8ae8d2e3fcb6f5d3f9165c12d4551a065d989cc" - integrity sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw= - dependencies: - through2 "^2.0.0" - vinyl "^1.1.0" - vinyl-file "^2.0.0" - -mem@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" - integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= - dependencies: - mimic-fn "^1.0.0" - mem@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf" @@ -20434,7 +19745,7 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.0.0, meow@^3.3.0, meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -20654,7 +19965,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@3.0.x, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -21044,16 +20355,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multimatch@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" - integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= - dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" - multimatch@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" @@ -21113,11 +20414,6 @@ mute-stream@0.0.5: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= -mute-stream@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" - integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s= - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -21592,15 +20888,6 @@ normalize-url@1.9.1: query-string "^4.1.0" sort-keys "^1.0.0" -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - normalize-url@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -21618,34 +20905,11 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" -npm-conf@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - -npm-keyword@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/npm-keyword/-/npm-keyword-5.0.0.tgz#99b85aec29fcb388d2dd351f0013bf5268787e67" - integrity sha1-mbha7Cn8s4jS3TUfABO/Umh4fmc= - dependencies: - got "^7.1.0" - registry-url "^3.0.3" - npm-normalize-package-bin@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== -npm-run-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f" - integrity sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8= - dependencies: - path-key "^1.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -21686,15 +20950,6 @@ npmconf@^2.1.3: gauge "~2.7.3" set-blocking "~2.0.0" -npmlog@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-2.0.4.tgz#98b52530f2514ca90d09ec5b22c8846722375692" - integrity sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI= - dependencies: - ansi "~0.3.1" - are-we-there-yet "~1.1.2" - gauge "~1.2.5" - nth-check@^1.0.2, nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -21772,11 +21027,6 @@ object-assign@4.X, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= - object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" @@ -21843,11 +21093,6 @@ object-path-immutable@^3.1.1: dependencies: is-plain-object "3.0.0" -object-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/object-values/-/object-values-1.0.0.tgz#72af839630119e5b98c3b02bb8c27e3237158105" - integrity sha1-cq+DljARnluYw7AruMJ+MjcVgQU= - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -22047,7 +21292,7 @@ opentracing@^0.14.3: resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.4.tgz#a113408ea740da3a90fde5b3b0011a375c2e4268" integrity sha512-nNnZDkUNExBwEpb7LZaeMeQgvrlO8l4bgY/LvGNZCR0xG/dGWqHqjKrAmR5GUoYo0FIz38kxasvA1aevxWs2CA== -opn@^5.3.0, opn@^5.5.0: +opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== @@ -22172,14 +21417,6 @@ os-locale@^3.1.0: lcid "^2.0.0" mem "^4.0.0" -os-name@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-2.0.1.tgz#b9a386361c17ae3a21736ef0599405c9a8c5dc5e" - integrity sha1-uaOGNhwXrjohc27wWZQFyajF3F4= - dependencies: - macos-release "^1.0.0" - win-release "^1.0.0" - os-name@^3.0.0, os-name@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -22230,23 +21467,6 @@ p-all@^2.1.0: dependencies: p-map "^2.0.0" -p-any@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-any/-/p-any-1.1.0.tgz#1d03835c7eed1e34b8e539c47b7b60d0d015d4e1" - integrity sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g== - dependencies: - p-some "^2.0.0" - -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== - -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -22374,20 +21594,6 @@ p-retry@^4.2.0: "@types/retry" "^0.12.0" retry "^0.12.0" -p-some@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-some/-/p-some-2.0.1.tgz#65d87c8b154edbcf5221d167778b6d2e150f6f06" - integrity sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY= - dependencies: - aggregate-error "^1.0.0" - -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y= - dependencies: - p-finally "^1.0.0" - p-timeout@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" @@ -22423,26 +21629,6 @@ package-json@^1.0.0: got "^3.2.0" registry-url "^3.0.0" -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= - dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - -package-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-5.0.0.tgz#a7dbe2725edcc7dc9bcee627672275e323882433" - integrity sha512-EeHQFFTlEmLrkIQoxbE9w0FuAWHoc1XpthDqnZ/i9keOt701cteyXwAxQFLpVqVjj3feh2TodkihjLaRUtIgLg== - dependencies: - got "^8.3.1" - registry-auth-token "^3.3.2" - registry-url "^3.1.0" - semver "^5.5.0" - package-json@^6.3.0: version "6.5.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" @@ -22453,11 +21639,6 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -pad-component@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/pad-component/-/pad-component-0.0.1.tgz#ad1f22ce1bf0fdc0d6ddd908af17f351a404b8ac" - integrity sha1-rR8izhvw/cDW3dkIrxfzUaQEuKw= - pad-right@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774" @@ -22600,13 +21781,6 @@ parse-headers@^2.0.0: for-each "^0.3.2" trim "0.0.1" -parse-help@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-help/-/parse-help-1.0.0.tgz#a260363ec71a96c0bad4a2ce0208c14a35dd0349" - integrity sha512-dlOrbBba6Rrw/nrJ+V7/vkGZdiimWJQzMHZZrYsUq03JE8AV3fAv6kOYX7dP/w2h67lIdmRf8ES8mU44xAgE/Q== - dependencies: - execall "^1.0.0" - parse-json@^2.1.0, parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -22715,14 +21889,6 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -passwd-user@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/passwd-user/-/passwd-user-2.1.0.tgz#fad9db6ae252f8b088e0c5decd20a7da0c5d9f1e" - integrity sha1-+tnbauJS+LCI4MXezSCn2gxdnx4= - dependencies: - execa "^0.4.0" - pify "^2.3.0" - password-prompt@^1.0.7: version "1.1.2" resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" @@ -22775,11 +21941,6 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= -path-key@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" - integrity sha1-XVPVeAGWRsDWiADbThRua9wqx68= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -22956,7 +22117,7 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: +pify@^2.0.0, pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -23295,11 +22456,6 @@ prettier@~2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -pretty-bytes@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" - integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk= - pretty-bytes@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -23675,15 +22831,6 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - query-string@^6.13.2: version "6.13.2" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.2.tgz#3585aa9412c957cbd358fd5eaca7466f05586dda" @@ -23845,7 +22992,7 @@ rbush@^3.0.1: dependencies: quickselect "^2.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: +rc@^1.0.1, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -24582,14 +23729,6 @@ read-all-stream@^3.0.0: pinkie-promise "^2.0.0" readable-stream "^2.0.0" -read-chunk@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655" - integrity sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU= - dependencies: - pify "^3.0.0" - safe-buffer "^5.1.1" - read-installed@~4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" @@ -24639,14 +23778,6 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" - read-pkg "^3.0.0" - read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" @@ -24674,15 +23805,6 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" @@ -25035,14 +24157,6 @@ regexpu-core@^4.7.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" -registry-auth-token@^3.0.1, registry-auth-token@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" - integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== - dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" - registry-auth-token@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" @@ -25050,7 +24164,7 @@ registry-auth-token@^4.0.0: dependencies: rc "^1.2.8" -registry-url@^3.0.0, registry-url@^3.0.3, registry-url@^3.1.0: +registry-url@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= @@ -25302,11 +24416,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= - replace-ext@1.0.0, replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" @@ -25361,7 +24470,7 @@ request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.81.0, request@2.88.0, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@2.81.0, request@2.88.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -25579,7 +24688,7 @@ resolve@~1.10.1: dependencies: path-parse "^1.0.6" -responselike@1.0.2, responselike@^1.0.2: +responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -25658,7 +24767,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.6.3, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: +rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -25722,14 +24831,6 @@ rollup@^0.25.8: minimist "^1.2.0" source-map-support "^0.3.2" -root-check@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/root-check/-/root-check-1.0.0.tgz#c52a794bf0db9fad567536e41898f0c9e0a86697" - integrity sha1-xSp5S/Dbn61WdTbkGJjwyeCoZpc= - dependencies: - downgrade-root "^1.0.0" - sudo-block "^1.1.0" - rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -25757,7 +24858,7 @@ run-async@^0.1.0: dependencies: once "^1.3.0" -run-async@^2.0.0, run-async@^2.2.0, run-async@^2.4.0: +run-async@^2.2.0, run-async@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== @@ -25791,11 +24892,6 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" - integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= - rxjs-marbles@^5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/rxjs-marbles/-/rxjs-marbles-5.0.6.tgz#e8e71df3b82b49603555f017f2fd3d8c359c4c24" @@ -25803,13 +24899,6 @@ rxjs-marbles@^5.0.6: dependencies: fast-equals "^2.0.0" -rxjs@^5.5.2: - version "5.5.12" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" - integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== - dependencies: - symbol-observable "1.0.1" - rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.5.3, rxjs@^6.5.5, rxjs@^6.6.0: version "6.6.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" @@ -25996,11 +25085,6 @@ scope-analyzer@^2.0.1: estree-is-function "^1.0.0" get-assigned-identifiers "^1.1.0" -scoped-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8" - integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg= - screenfull@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.0.tgz#5c2010c0e84fd4157bf852877698f90b8cbe96f6" @@ -26076,24 +25160,12 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -semver-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" - integrity sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk= - -semver-truncate@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-1.1.2.tgz#57f41de69707a62709a7e0104ba2117109ea47e8" - integrity sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g= - dependencies: - semver "^5.3.0" - "semver@2 || 3 || 4": version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= -"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -26225,7 +25297,7 @@ set-harmonic-interval@^1.0.1: resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== -set-immediate-shim@^1.0.0, set-immediate-shim@~1.0.1: +set-immediate-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= @@ -26359,15 +25431,6 @@ shelljs@^0.6.0: resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" integrity sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg= -shelljs@^0.7.0: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM= - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shelljs@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" @@ -26574,14 +25637,6 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -sort-on@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/sort-on/-/sort-on-3.0.0.tgz#8094005281bf450e91ac4cb4c4cf00c3d6569c41" - integrity sha512-e2RHeY1iM6dT9od3RoqeJSyz3O7naNFsGy34+EFEcwghjAncuOXC2/Xwq87S4FbypqLVp6PcizYEsGEGsGIDXA== - dependencies: - arrify "^1.0.0" - dot-prop "^4.1.1" - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -27130,14 +26185,6 @@ string-length@^1.0.0: dependencies: strip-ansi "^3.0.0" -string-length@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" - integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= - dependencies: - astral-regex "^1.0.0" - strip-ansi "^4.0.0" - string-length@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" @@ -27330,14 +26377,6 @@ strip-ansi@~0.1.0: resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE= -strip-bom-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca" - integrity sha1-+H217yYT9paKpUWr/h7HKLaoKco= - dependencies: - first-chunk-stream "^2.0.0" - strip-bom "^2.0.0" - strip-bom-string@1.X: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" @@ -27471,15 +26510,6 @@ success-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897" integrity sha1-JAIuSG878c3KCUKDt2nEctO3KJc= -sudo-block@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/sudo-block/-/sudo-block-1.2.0.tgz#cc539bf8191624d4f507d83eeb45b4cea27f3463" - integrity sha1-zFOb+BkWJNT1B9g+60W0zqJ/NGM= - dependencies: - chalk "^1.0.0" - is-docker "^1.0.0" - is-root "^1.0.0" - superagent@3.8.2: version "3.8.2" resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" @@ -27559,13 +26589,6 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.1.2: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= - dependencies: - has-flag "^1.0.0" - supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -27650,11 +26673,6 @@ swap-case@^1.1.0: lower-case "^1.1.1" upper-case "^1.1.1" -symbol-observable@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" - integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= - symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -27711,26 +26729,6 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" -tabtab@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/tabtab/-/tabtab-1.3.2.tgz#bb9c2ca6324f659fde7634c2caf3c096e1187ca7" - integrity sha1-u5wspjJPZZ/edjTCyvPAluEYfKc= - dependencies: - debug "^2.2.0" - inquirer "^1.0.2" - minimist "^1.2.0" - mkdirp "^0.5.1" - npmlog "^2.0.3" - object-assign "^4.1.0" - -taketalk@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/taketalk/-/taketalk-1.0.0.tgz#b4d4f0deed206ae7df775b129ea2ca6de52f26dd" - integrity sha1-tNTw3u0gauffd1sSnqLKbeUvJt0= - dependencies: - get-stdin "^4.0.1" - minimist "^1.1.0" - tapable@^0.1.8: version "0.1.10" resolved "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" @@ -27891,13 +26889,6 @@ tempy@^0.3.0: type-fest "^0.3.1" unique-string "^1.0.0" -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" - term-size@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" @@ -27989,11 +26980,6 @@ text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -textextensions@2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.2.0.tgz#38ac676151285b658654581987a0ce1a4490d286" - integrity sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA== - thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -28074,11 +27060,6 @@ timed-out@^2.0.0: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" integrity sha1-84sK6B03R9YoAB9B2vxlKs5nHAo= -timed-out@^4.0.0, timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - timers-browserify@^1.0.1: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -28174,11 +27155,6 @@ title-case@^2.1.0, title-case@^2.1.1: no-case "^2.2.0" upper-case "^1.0.3" -titleize@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-1.0.1.tgz#21bc24fcca658eadc6d3bd3c38f2bd173769b4c5" - integrity sha512-rUwGDruKq1gX+FFHbTl5qjI7teVO7eOe+C8IcQ7QT+1BK3eEUXJqbZcBOeaRP4FwSC/C1A5jDoIVta0nIQ9yew== - tmp@0.0.30: version "0.0.30" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed" @@ -28186,13 +27162,6 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@^0.0.29: - version "0.0.29" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" - integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA= - dependencies: - os-tmpdir "~1.0.1" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -28348,7 +27317,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -28566,25 +27535,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -twig@^1.10.5: - version "1.12.0" - resolved "https://registry.yarnpkg.com/twig/-/twig-1.12.0.tgz#04450bf18ee05532ff70098f10b07227f956b8cf" - integrity sha512-zm5OQXb8bQDGQUPytFgjqMKHhqcz/s6pU6Nwsy+rKPhsoOOVwYeHnziiDGFzeTDiFd28M8EVkEO8we6ikcrGjQ== - dependencies: - locutus "^2.0.5" - minimatch "3.0.x" - walk "2.3.x" - type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -29156,18 +28111,6 @@ unstated@^2.1.1: dependencies: create-react-context "^0.1.5" -untildify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0" - integrity sha1-F+soB5h/dpUunASF/DEdBqgmouA= - dependencies: - os-homedir "^1.0.0" - -untildify@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" - integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -29178,11 +28121,6 @@ unzip-response@^1.0.0: resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" integrity sha1-uYTwh3/AqJwsdzzB73tbIytbBv4= -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= - upath@^1.1.0, upath@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" @@ -29201,22 +28139,6 @@ update-notifier@^0.5.0: semver-diff "^2.0.0" string-length "^1.0.0" -update-notifier@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== - dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" - update-notifier@^4.0.0, update-notifier@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" @@ -29310,11 +28232,6 @@ url-template@^2.0.8: resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= - url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -29479,7 +28396,7 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^3.0.0, uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: +uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -30004,18 +28921,6 @@ vfile@^4.0.0, vfile@^4.2.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -vinyl-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a" - integrity sha1-p+v1/779obfRjRQPyweyI++2dRo= - dependencies: - graceful-fs "^4.1.2" - pify "^2.3.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - strip-bom-stream "^2.0.0" - vinyl "^1.1.0" - vinyl-fs@^3.0.0, vinyl-fs@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" @@ -30059,16 +28964,7 @@ vinyl-sourcemaps-apply@^0.2.0: dependencies: source-map "^0.5.1" -vinyl@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^2.0.0, vinyl@^2.0.1, vinyl@^2.1.0, vinyl@^2.2.0: +vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== @@ -30164,7 +29060,7 @@ wait-on@^5.0.1: minimist "^1.2.5" rxjs "^6.5.5" -walk@2.3.x, walk@^2.3.14: +walk@^2.3.14: version "2.3.14" resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.14.tgz#60ec8631cfd23276ae1e7363ce11d626452e1ef3" integrity sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg== @@ -30523,7 +29419,7 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@1, which@1.3.1, which@^1.2.14, which@^1.2.8, which@^1.2.9, which@^1.3.1, which@~1.3.0: +which@1, which@1.3.1, which@^1.2.14, which@^1.2.9, which@^1.3.1, which@~1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -30544,7 +29440,7 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -widest-line@^2.0.0, widest-line@^2.0.1: +widest-line@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== @@ -30558,13 +29454,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -win-release@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" - integrity sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk= - dependencies: - semver "^5.0.1" - window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" @@ -30715,7 +29604,7 @@ write-file-atomic@^1.1.2: imurmurhash "^0.1.4" slide "^1.1.5" -write-file-atomic@^2.0.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: +write-file-atomic@^2.4.2: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== @@ -30798,11 +29687,6 @@ xdg-basedir@^2.0.0: dependencies: os-homedir "^1.0.0" -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -31080,155 +29964,6 @@ yazl@^2.5.1: dependencies: buffer-crc32 "~0.2.3" -yeoman-character@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/yeoman-character/-/yeoman-character-1.1.0.tgz#90d4b5beaf92759086177015b2fdfa2e0684d7c7" - integrity sha1-kNS1vq+SdZCGF3AVsv36LgaE18c= - dependencies: - supports-color "^3.1.2" - -yeoman-doctor@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yeoman-doctor/-/yeoman-doctor-3.0.2.tgz#b9ad20422ba84b3ef03c1d4afa73cc90fa48fe51" - integrity sha512-/KbouQdKgnqxG6K3Tc8VBPAQLPbruQ7KkbinwR+ah507oOFobHnGs8kqj8oMfafY6rXInHdh7nC5YzicCR4Z0g== - dependencies: - ansi-styles "^3.2.0" - bin-version-check "^3.0.0" - chalk "^2.3.0" - each-async "^1.1.1" - latest-version "^3.1.0" - log-symbols "^2.1.0" - object-values "^1.0.0" - semver "^5.0.3" - twig "^1.10.5" - user-home "^2.0.0" - -yeoman-environment@^1.1.0: - version "1.6.6" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-1.6.6.tgz#cd85fa67d156060e440d7807d7ef7cf0d2d1d671" - integrity sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE= - dependencies: - chalk "^1.0.0" - debug "^2.0.0" - diff "^2.1.2" - escape-string-regexp "^1.0.2" - globby "^4.0.0" - grouped-queue "^0.3.0" - inquirer "^1.0.2" - lodash "^4.11.1" - log-symbols "^1.0.1" - mem-fs "^1.1.0" - text-table "^0.2.0" - untildify "^2.0.0" - -yeoman-environment@^2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.3.4.tgz#ae156147a1b85de939366e5438b00cb3eb54c3e9" - integrity sha512-KLxE5ft/74Qj7h3AsQZv8G6MEEHYJwmD5F99nfOVaep3rBzCtbrJKkdqWc7bDV141Nr8UZZsIXmzc3IcCm6E2w== - dependencies: - chalk "^2.4.1" - cross-spawn "^6.0.5" - debug "^3.1.0" - diff "^3.5.0" - escape-string-regexp "^1.0.2" - globby "^8.0.1" - grouped-queue "^0.3.3" - inquirer "^6.0.0" - is-scoped "^1.0.0" - lodash "^4.17.10" - log-symbols "^2.2.0" - mem-fs "^1.1.0" - strip-ansi "^4.0.0" - text-table "^0.2.0" - untildify "^3.0.3" - -yeoman-generator@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yeoman-generator/-/yeoman-generator-1.1.1.tgz#40c2b4f6cdfbe05e1952fdd72933f0d8925dbdf5" - integrity sha1-QMK09s374F4ZUv3XKTPw2JJdvfU= - dependencies: - async "^2.0.0" - chalk "^1.0.0" - class-extend "^0.1.0" - cli-table "^0.3.1" - cross-spawn "^5.0.1" - dargs "^5.1.0" - dateformat "^2.0.0" - debug "^2.1.0" - detect-conflict "^1.0.0" - error "^7.0.2" - find-up "^2.1.0" - github-username "^3.0.0" - glob "^7.0.3" - istextorbinary "^2.1.0" - lodash "^4.11.1" - mem-fs-editor "^3.0.0" - minimist "^1.2.0" - mkdirp "^0.5.0" - path-exists "^3.0.0" - path-is-absolute "^1.0.0" - pretty-bytes "^4.0.2" - read-chunk "^2.0.0" - read-pkg-up "^2.0.0" - rimraf "^2.2.0" - run-async "^2.0.0" - shelljs "^0.7.0" - text-table "^0.2.0" - through2 "^2.0.0" - user-home "^2.0.0" - yeoman-environment "^1.1.0" - -yo@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/yo/-/yo-2.0.6.tgz#7b562f68a0434237c24a1fd3982f235035839516" - integrity sha512-1OleNumZXtE/Lo/ZDPsMXqOX8oXr8tpBXYgUGEDAONYqLX3/n3PV3BWkXI7Iwq6vhuAAYPRLinncUe30izlcSg== - dependencies: - async "^2.6.1" - chalk "^2.4.1" - cli-list "^0.2.0" - configstore "^3.1.2" - cross-spawn "^6.0.5" - figures "^2.0.0" - fullname "^3.2.0" - global-tunnel-ng "^2.5.3" - got "^8.3.2" - humanize-string "^1.0.2" - inquirer "^6.0.0" - insight "0.10.1" - lodash "^4.17.10" - meow "^3.0.0" - npm-keyword "^5.0.0" - opn "^5.3.0" - package-json "^5.0.0" - parse-help "^1.0.0" - read-pkg-up "^4.0.0" - root-check "^1.0.0" - sort-on "^3.0.0" - string-length "^2.0.0" - tabtab "^1.3.2" - titleize "^1.0.1" - update-notifier "^2.5.0" - user-home "^2.0.0" - yeoman-character "^1.0.0" - yeoman-doctor "^3.0.1" - yeoman-environment "^2.3.0" - yosay "^2.0.2" - -yosay@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/yosay/-/yosay-2.0.2.tgz#a7017e764cd88d64a1ae64812201de5b157adf6d" - integrity sha512-avX6nz2esp7IMXGag4gu6OyQBsMh/SEn+ZybGu3yKPlOTE6z9qJrzG/0X5vCq/e0rPFy0CUYCze0G5hL310ibA== - dependencies: - ansi-regex "^2.0.0" - ansi-styles "^3.0.0" - chalk "^1.0.0" - cli-boxes "^1.0.0" - pad-component "0.0.1" - string-width "^2.0.0" - strip-ansi "^3.0.0" - taketalk "^1.0.0" - wrap-ansi "^2.0.0" - z-schema@~3.18.3: version "3.18.4" resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2" From b19a342deffd0faf356c35c3f7637260e67f15ee Mon Sep 17 00:00:00 2001 From: John Schulz Date: Fri, 6 Nov 2020 12:14:03 -0500 Subject: [PATCH 36/81] Move parseAndVerify* functions to validation.ts (#82845) ## Summary Basic cut-and-paste of `parseAndVerify*` functions from `archive/index.ts` to `archive/validation.ts`. Should be easier to mock now, replace later, etc. --- .../server/services/epm/archive/index.ts | 265 +----------------- .../server/services/epm/archive/validation.ts | 262 +++++++++++++++++ 2 files changed, 265 insertions(+), 262 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts index fe5a395804932c..ee505b205fc847 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts @@ -4,21 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import yaml from 'js-yaml'; -import { uniq } from 'lodash'; - -import { - ArchivePackage, - RegistryPolicyTemplate, - RegistryDataStream, - RegistryInput, - RegistryStream, - RegistryVarsEntry, -} from '../../../../common/types'; +import { ArchivePackage } from '../../../../common/types'; import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors'; -import { pkgToPkgKey } from '../registry'; -import { cacheGet, cacheSet, setArchiveFilelist } from '../registry/cache'; +import { cacheSet, setArchiveFilelist } from '../registry/cache'; import { ArchiveEntry, getBufferExtractor } from '../registry/extract'; +import { parseAndVerifyArchive } from './validation'; export async function loadArchivePackage({ archiveBuffer, @@ -74,252 +64,3 @@ export async function unpackArchiveToCache( } return paths; } - -// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the -// package registry. At some point this should probably be replaced (or enhanced) with verification based on -// https://github.com/elastic/package-spec/ - -function parseAndVerifyArchive(paths: string[]): ArchivePackage { - // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present - const toplevelDir = paths[0].split('/')[0]; - paths.forEach((path) => { - if (path.split('/')[0] !== toplevelDir) { - throw new PackageInvalidArchiveError('Package contains more than one top-level directory.'); - } - }); - - // The package must contain a manifest file ... - const manifestFile = `${toplevelDir}/manifest.yml`; - const manifestBuffer = cacheGet(manifestFile); - if (!paths.includes(manifestFile) || !manifestBuffer) { - throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.'); - } - - // ... which must be valid YAML - let manifest; - try { - manifest = yaml.load(manifestBuffer.toString()); - } catch (error) { - throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); - } - - // Package name and version from the manifest must match those from the toplevel directory - const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); - if (toplevelDir !== pkgKey) { - throw new PackageInvalidArchiveError( - `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` - ); - } - - const { name, version, description, type, categories, format_version: formatVersion } = manifest; - // check for mandatory fields - if (!(name && version && description && type && categories && formatVersion)) { - throw new PackageInvalidArchiveError( - 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' - ); - } - - const dataStreams = parseAndVerifyDataStreams(paths, name, version); - const policyTemplates = parseAndVerifyPolicyTemplates(manifest); - - return { - name, - version, - description, - type, - categories, - format_version: formatVersion, - data_streams: dataStreams, - policy_templates: policyTemplates, - }; -} - -function parseAndVerifyDataStreams( - paths: string[], - pkgName: string, - pkgVersion: string -): RegistryDataStream[] { - // A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml - let dataStreamPaths: string[] = []; - const dataStreams: RegistryDataStream[] = []; - const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion }); - - // pick all paths matching name-version/data_stream/DATASTREAM_PATH/... - // from those, pick all unique data stream paths - paths - .filter((path) => path.startsWith(`${pkgKey}/data_stream/`)) - .forEach((path) => { - const parts = path.split('/'); - if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]); - }); - - dataStreamPaths = uniq(dataStreamPaths); - - dataStreamPaths.forEach((dataStreamPath) => { - const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`; - const manifestBuffer = cacheGet(manifestFile); - if (!paths.includes(manifestFile) || !manifestBuffer) { - throw new PackageInvalidArchiveError( - `No manifest.yml file found for data stream '${dataStreamPath}'` - ); - } - - let manifest; - try { - manifest = yaml.load(manifestBuffer.toString()); - } catch (error) { - throw new PackageInvalidArchiveError( - `Could not parse package manifest for data stream '${dataStreamPath}': ${error}.` - ); - } - - const { - title: dataStreamTitle, - release, - ingest_pipeline: ingestPipeline, - type, - dataset, - } = manifest; - if (!(dataStreamTitle && release && type)) { - throw new PackageInvalidArchiveError( - `Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'` - ); - } - const streams = parseAndVerifyStreams(manifest, dataStreamPath); - - // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26 - return dataStreams.push({ - dataset: dataset || `${pkgName}.${dataStreamPath}`, - title: dataStreamTitle, - release, - package: pkgName, - ingest_pipeline: ingestPipeline || 'default', - path: dataStreamPath, - type, - streams, - }); - }); - - return dataStreams; -} - -function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] { - const streams: RegistryStream[] = []; - const manifestStreams = manifest.streams; - if (manifestStreams && manifestStreams.length > 0) { - manifestStreams.forEach((manifestStream: any) => { - const { - input, - title: streamTitle, - description, - enabled, - vars: manifestVars, - template_path: templatePath, - } = manifestStream; - if (!(input && streamTitle)) { - throw new PackageInvalidArchiveError( - `Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title` - ); - } - const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`); - // default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143 - streams.push({ - input, - title: streamTitle, - description, - enabled, - vars, - template_path: templatePath || 'stream.yml.hbs', - }); - }); - } - return streams; -} - -function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] { - const vars: RegistryVarsEntry[] = []; - if (manifestVars && manifestVars.length > 0) { - manifestVars.forEach((manifestVar) => { - const { - name, - title: varTitle, - description, - type, - required, - show_user: showUser, - multi, - def, - os, - } = manifestVar; - if (!(name && type)) { - throw new PackageInvalidArchiveError( - `Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}` - ); - } - vars.push({ - name, - title: varTitle, - description, - type, - required, - show_user: showUser, - multi, - default: def, - os, - }); - }); - } - return vars; -} - -function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] { - const policyTemplates: RegistryPolicyTemplate[] = []; - const manifestPolicyTemplates = manifest.policy_templates; - if (manifestPolicyTemplates && manifestPolicyTemplates > 0) { - manifestPolicyTemplates.forEach((policyTemplate: any) => { - const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate; - if (!(name && policyTemplateTitle && description && inputs)) { - throw new PackageInvalidArchiveError( - `Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}` - ); - } - - const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`); - - // defaults to true if undefined, but may be explicitly set to false. - let parsedMultiple = true; - if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false; - - policyTemplates.push({ - name, - title: policyTemplateTitle, - description, - inputs: parsedInputs, - multiple: parsedMultiple, - }); - }); - } - return policyTemplates; -} - -function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] { - const inputs: RegistryInput[] = []; - if (manifestInputs && manifestInputs.length > 0) { - manifestInputs.forEach((input: any) => { - const { type, title: inputTitle, description, vars } = input; - if (!(type && inputTitle)) { - throw new PackageInvalidArchiveError( - `Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}` - ); - } - const parsedVars = parseAndVerifyVars(vars, location); - inputs.push({ - type, - title: inputTitle, - description, - vars: parsedVars, - }); - }); - } - return inputs; -} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts new file mode 100644 index 00000000000000..e83340124a2d03 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 yaml from 'js-yaml'; +import { uniq } from 'lodash'; +import { + ArchivePackage, + RegistryPolicyTemplate, + RegistryDataStream, + RegistryInput, + RegistryStream, + RegistryVarsEntry, +} from '../../../../common/types'; +import { PackageInvalidArchiveError } from '../../../errors'; +import { pkgToPkgKey } from '../registry'; +import { cacheGet } from '../registry/cache'; + +// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the +// package registry. At some point this should probably be replaced (or enhanced) with verification based on +// https://github.com/elastic/package-spec/ +export function parseAndVerifyArchive(paths: string[]): ArchivePackage { + // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present + const toplevelDir = paths[0].split('/')[0]; + paths.forEach((path) => { + if (path.split('/')[0] !== toplevelDir) { + throw new PackageInvalidArchiveError('Package contains more than one top-level directory.'); + } + }); + + // The package must contain a manifest file ... + const manifestFile = `${toplevelDir}/manifest.yml`; + const manifestBuffer = cacheGet(manifestFile); + if (!paths.includes(manifestFile) || !manifestBuffer) { + throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.'); + } + + // ... which must be valid YAML + let manifest; + try { + manifest = yaml.load(manifestBuffer.toString()); + } catch (error) { + throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); + } + + // Package name and version from the manifest must match those from the toplevel directory + const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); + if (toplevelDir !== pkgKey) { + throw new PackageInvalidArchiveError( + `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` + ); + } + + const { name, version, description, type, categories, format_version: formatVersion } = manifest; + // check for mandatory fields + if (!(name && version && description && type && categories && formatVersion)) { + throw new PackageInvalidArchiveError( + 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' + ); + } + + const dataStreams = parseAndVerifyDataStreams(paths, name, version); + const policyTemplates = parseAndVerifyPolicyTemplates(manifest); + + return { + name, + version, + description, + type, + categories, + format_version: formatVersion, + data_streams: dataStreams, + policy_templates: policyTemplates, + }; +} +function parseAndVerifyDataStreams( + paths: string[], + pkgName: string, + pkgVersion: string +): RegistryDataStream[] { + // A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml + let dataStreamPaths: string[] = []; + const dataStreams: RegistryDataStream[] = []; + const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion }); + + // pick all paths matching name-version/data_stream/DATASTREAM_PATH/... + // from those, pick all unique data stream paths + paths + .filter((path) => path.startsWith(`${pkgKey}/data_stream/`)) + .forEach((path) => { + const parts = path.split('/'); + if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]); + }); + + dataStreamPaths = uniq(dataStreamPaths); + + dataStreamPaths.forEach((dataStreamPath) => { + const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`; + const manifestBuffer = cacheGet(manifestFile); + if (!paths.includes(manifestFile) || !manifestBuffer) { + throw new PackageInvalidArchiveError( + `No manifest.yml file found for data stream '${dataStreamPath}'` + ); + } + + let manifest; + try { + manifest = yaml.load(manifestBuffer.toString()); + } catch (error) { + throw new PackageInvalidArchiveError( + `Could not parse package manifest for data stream '${dataStreamPath}': ${error}.` + ); + } + + const { + title: dataStreamTitle, + release, + ingest_pipeline: ingestPipeline, + type, + dataset, + } = manifest; + if (!(dataStreamTitle && release && type)) { + throw new PackageInvalidArchiveError( + `Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'` + ); + } + const streams = parseAndVerifyStreams(manifest, dataStreamPath); + + // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26 + return dataStreams.push({ + dataset: dataset || `${pkgName}.${dataStreamPath}`, + title: dataStreamTitle, + release, + package: pkgName, + ingest_pipeline: ingestPipeline || 'default', + path: dataStreamPath, + type, + streams, + }); + }); + + return dataStreams; +} +function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] { + const streams: RegistryStream[] = []; + const manifestStreams = manifest.streams; + if (manifestStreams && manifestStreams.length > 0) { + manifestStreams.forEach((manifestStream: any) => { + const { + input, + title: streamTitle, + description, + enabled, + vars: manifestVars, + template_path: templatePath, + } = manifestStream; + if (!(input && streamTitle)) { + throw new PackageInvalidArchiveError( + `Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title` + ); + } + const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`); + // default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143 + streams.push({ + input, + title: streamTitle, + description, + enabled, + vars, + template_path: templatePath || 'stream.yml.hbs', + }); + }); + } + return streams; +} +function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] { + const vars: RegistryVarsEntry[] = []; + if (manifestVars && manifestVars.length > 0) { + manifestVars.forEach((manifestVar) => { + const { + name, + title: varTitle, + description, + type, + required, + show_user: showUser, + multi, + def, + os, + } = manifestVar; + if (!(name && type)) { + throw new PackageInvalidArchiveError( + `Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}` + ); + } + vars.push({ + name, + title: varTitle, + description, + type, + required, + show_user: showUser, + multi, + default: def, + os, + }); + }); + } + return vars; +} +function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] { + const policyTemplates: RegistryPolicyTemplate[] = []; + const manifestPolicyTemplates = manifest.policy_templates; + if (manifestPolicyTemplates && manifestPolicyTemplates > 0) { + manifestPolicyTemplates.forEach((policyTemplate: any) => { + const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate; + if (!(name && policyTemplateTitle && description && inputs)) { + throw new PackageInvalidArchiveError( + `Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}` + ); + } + + const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`); + + // defaults to true if undefined, but may be explicitly set to false. + let parsedMultiple = true; + if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false; + + policyTemplates.push({ + name, + title: policyTemplateTitle, + description, + inputs: parsedInputs, + multiple: parsedMultiple, + }); + }); + } + return policyTemplates; +} +function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] { + const inputs: RegistryInput[] = []; + if (manifestInputs && manifestInputs.length > 0) { + manifestInputs.forEach((input: any) => { + const { type, title: inputTitle, description, vars } = input; + if (!(type && inputTitle)) { + throw new PackageInvalidArchiveError( + `Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}` + ); + } + const parsedVars = parseAndVerifyVars(vars, location); + inputs.push({ + type, + title: inputTitle, + description, + vars: parsedVars, + }); + }); + } + return inputs; +} From a6b2a6ef5b591a8231ee7cd3864a8b9087cfc8a4 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 6 Nov 2020 18:50:17 +0100 Subject: [PATCH 37/81] [Uptime] Migrate to new es client (#82003) * migrate to new es client * fix tests * fix type * types * types * update * update * update * upadte * update snaps Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/empty_state.test.tsx.snap | 4 +- .../__tests__/empty_state.test.tsx | 4 +- .../empty_state/empty_state_error.tsx | 6 +- .../__tests__/monitor_list.test.tsx | 7 +- .../overview/monitor_list/monitor_list.tsx | 2 +- .../public/state/reducers/monitor_list.ts | 7 +- .../lib/adapters/framework/adapter_types.ts | 11 +- .../telemetry/kibana_telemetry_adapter.ts | 18 +- .../lib/alerts/__tests__/status_check.test.ts | 6 +- .../server/lib/alerts/duration_anomaly.ts | 103 ++-- .../uptime/server/lib/alerts/status_check.ts | 23 +- .../plugins/uptime/server/lib/alerts/tls.ts | 137 +++--- .../server/lib/alerts/uptime_alert_wrapper.ts | 22 +- .../get_monitor_charts.test.ts.snap | 4 - .../lib/requests/__tests__/get_certs.test.ts | 22 +- .../__tests__/get_latest_monitor.test.ts | 37 +- .../get_monitor_availability.test.ts | 37 +- .../__tests__/get_monitor_charts.test.ts | 20 +- .../__tests__/get_monitor_status.test.ts | 47 +- .../__tests__/get_ping_histogram.test.ts | 189 ++++---- .../lib/requests/__tests__/get_pings.test.ts | 71 ++- .../server/lib/requests/__tests__/helper.ts | 20 +- .../__tests__/monitor_charts_mock.json | 454 ++++++++++++------ .../uptime/server/lib/requests/get_certs.ts | 2 +- .../server/lib/requests/get_filter_bar.ts | 4 +- .../server/lib/requests/get_index_pattern.ts | 19 +- .../server/lib/requests/get_index_status.ts | 8 +- .../lib/requests/get_journey_screenshot.ts | 2 +- .../server/lib/requests/get_journey_steps.ts | 2 +- .../server/lib/requests/get_latest_monitor.ts | 2 +- .../lib/requests/get_monitor_availability.ts | 2 +- .../lib/requests/get_monitor_details.ts | 36 +- .../lib/requests/get_monitor_duration.ts | 2 +- .../lib/requests/get_monitor_locations.ts | 2 +- .../server/lib/requests/get_monitor_states.ts | 2 +- .../server/lib/requests/get_monitor_status.ts | 2 +- .../server/lib/requests/get_ping_histogram.ts | 2 +- .../uptime/server/lib/requests/get_pings.ts | 8 +- .../lib/requests/get_snapshot_counts.ts | 2 +- .../requests/search/find_potential_matches.ts | 2 +- .../lib/requests/search/query_context.ts | 8 +- .../search/refine_potential_matches.ts | 2 +- .../rest_api/index_state/get_index_pattern.ts | 3 +- .../server/rest_api/monitors/monitor_list.ts | 75 +-- .../rest_api/monitors/monitors_details.ts | 1 - .../plugins/uptime/server/rest_api/types.ts | 10 +- .../server/rest_api/uptime_route_wrapper.ts | 4 +- 47 files changed, 838 insertions(+), 615 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap index ab38ee9adc6c2b..6fcce75cea70e5 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap @@ -1823,9 +1823,7 @@ exports[`EmptyState component renders error message when an error occurs 1`] = `
-

+

There was an error fetching your data.

diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx index 6328789d03f29f..0de5cd3ab31be5 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx @@ -42,7 +42,9 @@ describe('EmptyState component', () => { it(`renders error message when an error occurs`, () => { const errors: IHttpFetchError[] = [ - new HttpFetchError('There was an error fetching your data.', 'error', {} as any), + new HttpFetchError('There was an error fetching your data.', 'error', {} as any, {} as any, { + body: { message: 'There was an error fetching your data.' }, + }), ]; const component = mountWithRouter( diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx index f7b77df8497f95..165b123d8884db 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx @@ -15,7 +15,7 @@ interface EmptyStateErrorProps { export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error: Error) => error.message && error.message.includes('unauthorized') + (error: IHttpFetchError) => error.message && error.message.includes('unauthorized') ); return ( @@ -46,7 +46,9 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { body={ {!unauthorized && - errors.map((error: Error) =>

{error.message}

)} + errors.map((error: IHttpFetchError) => ( +

{error.body.message || error.message}

+ ))}
} /> diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx index 1d8a7a771e0c51..352369cfdb72b8 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx @@ -17,6 +17,7 @@ import { MonitorListComponent, noItemsMessage } from '../monitor_list'; import { renderWithRouter, shallowWithRouter } from '../../../../lib'; import * as redux from 'react-redux'; import moment from 'moment'; +import { IHttpFetchError } from '../../../../../../../../src/core/public'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { @@ -187,7 +188,11 @@ describe('MonitorList component', () => { it('renders error list', () => { const component = shallowWithRouter( diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx index 5e0cc5d3dee1d4..f31e25484a9361 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx @@ -187,7 +187,7 @@ export const MonitorListComponent: ({ ( { @@ -41,7 +42,7 @@ export const monitorListReducer = handleActions( error: undefined, list: { ...action.payload }, }), - [String(getMonitorListFailure)]: (state: MonitorList, action: Action) => ({ + [String(getMonitorListFailure)]: (state: MonitorList, action: Action) => ({ ...state, error: action.payload, loading: false, diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index b75c729c2104a2..cd98ba1600d34b 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -9,17 +9,20 @@ import { IRouter, SavedObjectsClientContract, ISavedObjectsRepository, - ILegacyScopedClusterClient, + IScopedClusterClient, + ElasticsearchClient, } from 'src/core/server'; import { UMKibanaRoute } from '../../../rest_api'; import { PluginSetupContract } from '../../../../../features/server'; import { DynamicSettings } from '../../../../common/runtime_types'; import { MlPluginSetup as MlSetup } from '../../../../../ml/server'; -export type ESAPICaller = ILegacyScopedClusterClient['callAsCurrentUser']; - export type UMElasticsearchQueryFn = ( - params: { callES: ESAPICaller; dynamicSettings: DynamicSettings } & P + params: { + callES: ElasticsearchClient; + esClient?: IScopedClusterClient; + dynamicSettings: DynamicSettings; + } & P ) => Promise; export type UMSavedObjectsQueryFn = ( diff --git a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts index a8969f2621f292..2126b484b1cfd3 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts @@ -5,10 +5,14 @@ */ import moment from 'moment'; -import { ISavedObjectsRepository, SavedObjectsClientContract } from 'kibana/server'; +import { + ISavedObjectsRepository, + ILegacyScopedClusterClient, + SavedObjectsClientContract, + ElasticsearchClient, +} from 'kibana/server'; import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PageViewParams, UptimeTelemetry, Usage } from './types'; -import { ESAPICaller } from '../framework'; import { savedObjectsAdapter } from '../../saved_objects'; interface UptimeTelemetryCollector { @@ -21,6 +25,8 @@ const BUCKET_SIZE = 3600; const BUCKET_NUMBER = 24; export class KibanaTelemetryAdapter { + public static callCluster: ILegacyScopedClusterClient['callAsCurrentUser'] | ElasticsearchClient; + public static registerUsageCollector = ( usageCollector: UsageCollectionSetup, getSavedObjectsClient: () => ISavedObjectsRepository | undefined @@ -125,7 +131,7 @@ export class KibanaTelemetryAdapter { } public static async countNoOfUniqueMonitorAndLocations( - callCluster: ESAPICaller, + callCluster: ILegacyScopedClusterClient['callAsCurrentUser'] | ElasticsearchClient, savedObjectsClient: ISavedObjectsRepository | SavedObjectsClientContract ) { const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); @@ -187,7 +193,11 @@ export class KibanaTelemetryAdapter { }, }; - const result = await callCluster('search', params); + const { body: result } = + typeof callCluster === 'function' + ? await callCluster('search', params) + : await callCluster.search(params); + const numberOfUniqueMonitors: number = result?.aggregations?.unique_monitors?.value ?? 0; const numberOfUniqueLocations: number = result?.aggregations?.unique_locations?.value ?? 0; const monitorNameStats: any = result?.aggregations?.monitor_name; diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 06b298aedeb2b5..ccb1e5a40ad2de 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -56,6 +56,8 @@ const mockOptions = ( services = alertsMock.createAlertServices(), state = {} ): any => { + services.scopedClusterClient = jest.fn() as any; + services.savedObjectsClient.get.mockResolvedValue({ id: '', type: '', @@ -282,7 +284,8 @@ describe('status check alert', () => { expect.assertions(5); toISOStringSpy.mockImplementation(() => 'foo date string'); const mockGetter: jest.Mock = jest.fn(); - mockGetter.mockReturnValue([ + + mockGetter.mockReturnValueOnce([ { monitorId: 'first', location: 'harrisburg', @@ -326,6 +329,7 @@ describe('status check alert', () => { const state = await alert.executor(options); const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; expect(mockGetter).toHaveBeenCalledTimes(1); + expect(mockGetter.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index 9dddc0035f6902..d4c26fe83b5fc1 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -12,12 +12,12 @@ import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { commonStateTranslations, durationAnomalyTranslations } from './translations'; import { AnomaliesTableRecord } from '../../../../ml/common/types/anomalies'; import { getSeverityType } from '../../../../ml/common/util/anomaly_utils'; -import { savedObjectsAdapter } from '../saved_objects'; import { UptimeCorePlugins } from '../adapters/framework'; import { UptimeAlertTypeFactory } from './types'; import { Ping } from '../../../common/runtime_types/ping'; import { getMLJobId } from '../../../common/lib'; import { getLatestMonitor } from '../requests/get_latest_monitor'; +import { uptimeAlertWrapper } from './uptime_alert_wrapper'; const { DURATION_ANOMALY } = ACTION_GROUP_DEFINITIONS; @@ -61,61 +61,58 @@ const getAnomalies = async ( ); }; -export const durationAnomalyAlertFactory: UptimeAlertTypeFactory = (_server, _libs, plugins) => ({ - id: 'xpack.uptime.alerts.durationAnomaly', - name: durationAnomalyTranslations.alertFactoryName, - validate: { - params: schema.object({ - monitorId: schema.string(), - severity: schema.number(), - }), - }, - defaultActionGroupId: DURATION_ANOMALY.id, - actionGroups: [ - { - id: DURATION_ANOMALY.id, - name: DURATION_ANOMALY.name, +export const durationAnomalyAlertFactory: UptimeAlertTypeFactory = (_server, _libs, plugins) => + uptimeAlertWrapper({ + id: 'xpack.uptime.alerts.durationAnomaly', + name: durationAnomalyTranslations.alertFactoryName, + validate: { + params: schema.object({ + monitorId: schema.string(), + severity: schema.number(), + }), }, - ], - actionVariables: { - context: [], - state: [...durationAnomalyTranslations.actionVariables, ...commonStateTranslations], - }, - producer: 'uptime', - async executor(options) { - const { - services: { alertInstanceFactory, callCluster, savedObjectsClient }, - state, - params, - } = options; + defaultActionGroupId: DURATION_ANOMALY.id, + actionGroups: [ + { + id: DURATION_ANOMALY.id, + name: DURATION_ANOMALY.name, + }, + ], + actionVariables: { + context: [], + state: [...durationAnomalyTranslations.actionVariables, ...commonStateTranslations], + }, + async executor({ options, esClient, savedObjectsClient, dynamicSettings }) { + const { + services: { alertInstanceFactory }, + state, + params, + } = options; - const { anomalies } = - (await getAnomalies(plugins, savedObjectsClient, params, state.lastCheckedAt)) ?? {}; + const { anomalies } = + (await getAnomalies(plugins, savedObjectsClient, params, state.lastCheckedAt)) ?? {}; - const foundAnomalies = anomalies?.length > 0; + const foundAnomalies = anomalies?.length > 0; - if (foundAnomalies) { - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - savedObjectsClient - ); - const monitorInfo = await getLatestMonitor({ - dynamicSettings, - callES: callCluster, - dateStart: 'now-15m', - dateEnd: 'now', - monitorId: params.monitorId, - }); - anomalies.forEach((anomaly, index) => { - const alertInstance = alertInstanceFactory(DURATION_ANOMALY.id + index); - const summary = getAnomalySummary(anomaly, monitorInfo); - alertInstance.replaceState({ - ...updateState(state, false), - ...summary, + if (foundAnomalies) { + const monitorInfo = await getLatestMonitor({ + dynamicSettings, + callES: esClient, + dateStart: 'now-15m', + dateEnd: 'now', + monitorId: params.monitorId, + }); + anomalies.forEach((anomaly, index) => { + const alertInstance = alertInstanceFactory(DURATION_ANOMALY.id + index); + const summary = getAnomalySummary(anomaly, monitorInfo); + alertInstance.replaceState({ + ...updateState(state, false), + ...summary, + }); + alertInstance.scheduleActions(DURATION_ANOMALY.id); }); - alertInstance.scheduleActions(DURATION_ANOMALY.id); - }); - } + } - return updateState(state, foundAnomalies); - }, -}); + return updateState(state, foundAnomalies); + }, + }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 7feb916046e3a0..b1b3666b40dc6b 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -26,7 +26,6 @@ import { GetMonitorStatusResult } from '../requests/get_monitor_status'; import { UNNAMED_LOCATION } from '../../../common/constants'; import { uptimeAlertWrapper } from './uptime_alert_wrapper'; import { MonitorStatusTranslations } from '../../../common/translations'; -import { ESAPICaller } from '../adapters/framework'; import { getUptimeIndexPattern, IndexPatternTitleAndFields } from '../requests/get_index_pattern'; import { UMServerLibs } from '../lib'; @@ -81,7 +80,6 @@ export const generateFilterDSL = async ( export const formatFilterString = async ( dynamicSettings: DynamicSettings, - callES: ESAPICaller, esClient: ElasticsearchClient, filters: StatusCheckFilters, search: string, @@ -90,9 +88,8 @@ export const formatFilterString = async ( await generateFilterDSL( () => libs?.requests?.getIndexPattern - ? libs?.requests?.getIndexPattern({ callES, esClient, dynamicSettings }) + ? libs?.requests?.getIndexPattern({ esClient, dynamicSettings }) : getUptimeIndexPattern({ - callES, esClient, dynamicSettings, }), @@ -237,12 +234,15 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = ], state: [...commonMonitorStateI18, ...commonStateTranslations], }, - async executor( - { params: rawParams, state, services: { alertInstanceFactory } }, - callES, + async executor({ + options: { + params: rawParams, + state, + services: { alertInstanceFactory }, + }, esClient, - dynamicSettings - ) { + dynamicSettings, + }) { const { filters, search, @@ -258,7 +258,6 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = const filterString = await formatFilterString( dynamicSettings, - callES, esClient, filters, search, @@ -278,7 +277,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = // after that shouldCheckStatus should be explicitly false if (!(!oldVersionTimeRange && shouldCheckStatus === false)) { downMonitorsByLocation = await libs.requests.getMonitorStatus({ - callES, + callES: esClient, dynamicSettings, timerange, numTimes, @@ -311,7 +310,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = let availabilityResults: GetMonitorAvailabilityResult[] = []; if (shouldCheckAvailability) { availabilityResults = await libs.requests.getMonitorAvailability({ - callES, + callES: esClient, dynamicSettings, ...availability, filters: JSON.stringify(filterString) || undefined, diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index d4853ad7a9cb03..11f602d10bf51c 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -7,13 +7,13 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; import { UptimeAlertTypeFactory } from './types'; -import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; +import { uptimeAlertWrapper } from './uptime_alert_wrapper'; const { TLS } = ACTION_GROUP_DEFINITIONS; @@ -82,74 +82,73 @@ export const getCertSummary = ( }; }; -export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ - id: 'xpack.uptime.alerts.tls', - name: tlsTranslations.alertFactoryName, - validate: { - params: schema.object({}), - }, - defaultActionGroupId: TLS.id, - actionGroups: [ - { - id: TLS.id, - name: TLS.name, +export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => + uptimeAlertWrapper({ + id: 'xpack.uptime.alerts.tls', + name: tlsTranslations.alertFactoryName, + validate: { + params: schema.object({}), }, - ], - actionVariables: { - context: [], - state: [...tlsTranslations.actionVariables, ...commonStateTranslations], - }, - producer: 'uptime', - async executor(options) { - const { - services: { alertInstanceFactory, callCluster, savedObjectsClient }, - state, - } = options; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); - - const { certs, total }: CertResult = await libs.requests.getCerts({ - callES: callCluster, - dynamicSettings, - from: DEFAULT_FROM, - to: DEFAULT_TO, - index: 0, - size: DEFAULT_SIZE, - notValidAfter: `now+${ - dynamicSettings?.certExpirationThreshold ?? - DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold - }d`, - notValidBefore: `now-${ - dynamicSettings?.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold - }d`, - sortBy: 'common_name', - direction: 'desc', - }); - - const foundCerts = total > 0; - - if (foundCerts) { - const absoluteExpirationThreshold = moment() - .add( - dynamicSettings.certExpirationThreshold ?? - DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold, - 'd' - ) - .valueOf(); - const absoluteAgeThreshold = moment() - .subtract( - dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold, - 'd' - ) - .valueOf(); - const alertInstance = alertInstanceFactory(TLS.id); - const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); - alertInstance.replaceState({ - ...updateState(state, foundCerts), - ...summary, + defaultActionGroupId: TLS.id, + actionGroups: [ + { + id: TLS.id, + name: TLS.name, + }, + ], + actionVariables: { + context: [], + state: [...tlsTranslations.actionVariables, ...commonStateTranslations], + }, + async executor({ options, dynamicSettings, esClient }) { + const { + services: { alertInstanceFactory }, + state, + } = options; + + const { certs, total }: CertResult = await libs.requests.getCerts({ + callES: esClient, + dynamicSettings, + from: DEFAULT_FROM, + to: DEFAULT_TO, + index: 0, + size: DEFAULT_SIZE, + notValidAfter: `now+${ + dynamicSettings?.certExpirationThreshold ?? + DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold + }d`, + notValidBefore: `now-${ + dynamicSettings?.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold + }d`, + sortBy: 'common_name', + direction: 'desc', }); - alertInstance.scheduleActions(TLS.id); - } - return updateState(state, foundCerts); - }, -}); + const foundCerts = total > 0; + + if (foundCerts) { + const absoluteExpirationThreshold = moment() + .add( + dynamicSettings.certExpirationThreshold ?? + DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold, + 'd' + ) + .valueOf(); + const absoluteAgeThreshold = moment() + .subtract( + dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold, + 'd' + ) + .valueOf(); + const alertInstance = alertInstanceFactory(TLS.id); + const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); + alertInstance.replaceState({ + ...updateState(state, foundCerts), + ...summary, + }); + alertInstance.scheduleActions(TLS.id); + } + + return updateState(state, foundCerts); + }, + }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts index 390b6d347996c1..0961eb6557891e 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts @@ -4,18 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, ElasticsearchClient } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { AlertExecutorOptions, AlertType, AlertTypeState } from '../../../../alerts/server'; import { savedObjectsAdapter } from '../saved_objects'; import { DynamicSettings } from '../../../common/runtime_types'; export interface UptimeAlertType extends Omit { - executor: ( - options: AlertExecutorOptions, - callES: ILegacyScopedClusterClient['callAsCurrentUser'], - esClient: ElasticsearchClient, - dynamicSettings: DynamicSettings - ) => Promise; + executor: ({ + options, + esClient, + dynamicSettings, + }: { + options: AlertExecutorOptions; + esClient: ElasticsearchClient; + dynamicSettings: DynamicSettings; + savedObjectsClient: SavedObjectsClientContract; + }) => Promise; } export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ @@ -23,13 +27,13 @@ export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ producer: 'uptime', executor: async (options: AlertExecutorOptions) => { const { - services: { callCluster: callES, scopedClusterClient }, + services: { scopedClusterClient: esClient, savedObjectsClient }, } = options; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( options.services.savedObjectsClient ); - return uptimeAlert.executor(options, callES, scopedClusterClient, dynamicSettings); + return uptimeAlert.executor({ options, esClient, dynamicSettings, savedObjectsClient }); }, }); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap index 97b97f84407584..6ab55c2afdddab 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap @@ -2,7 +2,6 @@ exports[`ElasticsearchMonitorsAdapter getMonitorChartsData will provide expected filters 1`] = ` Array [ - "search", Object { "body": Object { "aggs": Object { @@ -26,9 +25,6 @@ Array [ "buckets": 25, "field": "@timestamp", }, - "date_histogram": Object { - "fixed_interval": "36000ms", - }, }, }, "query": Object { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts index 4faaed53bebf2f..c0b94b19b75825 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts @@ -6,10 +6,10 @@ import { getCerts } from '../get_certs'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getCerts', () => { let mockHits: any; - let mockCallES: jest.Mock; beforeEach(() => { mockHits = [ @@ -79,17 +79,20 @@ describe('getCerts', () => { }, }, ]; - mockCallES = jest.fn(); - mockCallES.mockImplementation(() => ({ - hits: { - hits: mockHits, - }, - })); }); it('parses query result and returns expected values', async () => { + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce({ + body: { + hits: { + hits: mockHits, + }, + }, + } as any); + const result = await getCerts({ - callES: mockCallES, + callES: mockEsClient, dynamicSettings: { heartbeatIndices: 'heartbeat*', certAgeThreshold: DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold, @@ -126,10 +129,9 @@ describe('getCerts', () => { "total": 0, } `); - expect(mockCallES.mock.calls).toMatchInlineSnapshot(` + expect(mockEsClient.search.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "search", Object { "body": Object { "_source": Array [ diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts index bd353b62df828e..9503174ed104c1 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts @@ -6,6 +6,7 @@ import { getLatestMonitor } from '../get_latest_monitor'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getLatestMonitor', () => { let expectedGetLatestSearchParams: any; @@ -44,29 +45,33 @@ describe('getLatestMonitor', () => { }, }; mockEsSearchResult = { - hits: { - hits: [ - { - _id: 'fejwio32', - _source: { - '@timestamp': '123456', - monitor: { - duration: { - us: 12345, + body: { + hits: { + hits: [ + { + _id: 'fejwio32', + _source: { + '@timestamp': '123456', + monitor: { + duration: { + us: 12345, + }, + id: 'testMonitor', + status: 'down', + type: 'http', }, - id: 'testMonitor', - status: 'down', - type: 'http', }, }, - }, - ], + ], + }, }, }; }); it('returns data in expected shape', async () => { - const mockEsClient = jest.fn(async (_request: any, _params: any) => mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); + const result = await getLatestMonitor({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -94,6 +99,6 @@ describe('getLatestMonitor', () => { expect(result.timestamp).toBe('123456'); expect(result.monitor).not.toBeFalsy(); expect(result?.monitor?.id).toBe('testMonitor'); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetLatestSearchParams); + expect(mockEsClient.search).toHaveBeenCalledWith(expectedGetLatestSearchParams); }); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts index 015d9a4925f3eb..e8df65d4101679 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts @@ -72,7 +72,7 @@ const genBucketItem = ({ describe('monitor availability', () => { describe('getMonitorAvailability', () => { it('applies bool filters to params', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -109,16 +109,15 @@ describe('monitor availability', () => { } }`; await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, filters: exampleFilter, range: 2, rangeUnit: 'w', threshold: '54', }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -245,7 +244,7 @@ describe('monitor availability', () => { }); it('fetches a single page of results', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -288,13 +287,12 @@ describe('monitor availability', () => { threshold: '69', }; const result = await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -458,7 +456,7 @@ describe('monitor availability', () => { }); it('fetches multiple pages', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -512,7 +510,7 @@ describe('monitor availability', () => { genBucketItem ); const result = await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, range: 3, rangeUnit: 'M', @@ -606,9 +604,8 @@ describe('monitor availability', () => { }, ] `); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(2); - expect(method).toEqual('search'); + const [params] = esMock.search.mock.calls[0]; + expect(esMock.search).toHaveBeenCalledTimes(2); expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -701,9 +698,9 @@ describe('monitor availability', () => { "index": "heartbeat-8*", } `); - expect(esMock.callAsCurrentUser.mock.calls[1]).toMatchInlineSnapshot(` + + expect(esMock.search.mock.calls[1]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggs": Object { @@ -803,7 +800,7 @@ describe('monitor availability', () => { }); it('does not overwrite filters', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -816,14 +813,14 @@ describe('monitor availability', () => { genBucketItem ); await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, range: 3, rangeUnit: 's', threshold: '99', filters: JSON.stringify({ bool: { filter: [{ term: { 'monitor.id': 'foo' } }] } }), }); - const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts index 2ebe670bc43c1d..9edd3e2e160d24 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts @@ -8,37 +8,37 @@ import { set } from '@elastic/safer-lodash-set'; import mockChartsData from './monitor_charts_mock.json'; import { getMonitorDurationChart } from '../get_monitor_duration'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('ElasticsearchMonitorsAdapter', () => { it('getMonitorChartsData will provide expected filters', async () => { expect.assertions(2); - const searchMock = jest.fn(); - const search = searchMock.bind({}); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); await getMonitorDurationChart({ - callES: search, + callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, monitorId: 'fooID', dateStart: 'now-15m', dateEnd: 'now', }); - expect(searchMock).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); // protect against possible rounding errors polluting the snapshot comparison set( - searchMock.mock.calls[0][1], + mockEsClient.search.mock.calls[0], 'body.aggs.timeseries.date_histogram.fixed_interval', '36000ms' ); - expect(searchMock.mock.calls[0]).toMatchSnapshot(); + expect(mockEsClient.search.mock.calls[0]).toMatchSnapshot(); }); it('inserts empty buckets for missing data', async () => { - const searchMock = jest.fn(); - searchMock.mockReturnValue(mockChartsData); - const search = searchMock.bind({}); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockChartsData as any); + expect( await getMonitorDurationChart({ - callES: search, + callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, monitorId: 'id', dateStart: 'now-15m', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts index e61d736e371061..949bc39f072593 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts @@ -42,7 +42,7 @@ const genBucketItem = ({ describe('getMonitorStatus', () => { it('applies bool filters to params', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [], genBucketItem ); @@ -78,7 +78,7 @@ describe('getMonitorStatus', () => { }, }; await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, filters: exampleFilter, locations: [], @@ -88,9 +88,8 @@ describe('getMonitorStatus', () => { to: 'now-1m', }, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -190,12 +189,12 @@ describe('getMonitorStatus', () => { }); it('applies locations to params', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [], genBucketItem ); await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, locations: ['fairbanks', 'harrisburg'], numTimes: 1, @@ -204,9 +203,8 @@ describe('getMonitorStatus', () => { to: 'now', }, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -291,7 +289,7 @@ describe('getMonitorStatus', () => { }); it('properly assigns filters for complex kuery filters', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [{ bucketCriteria: [] }], genBucketItem ); @@ -353,12 +351,12 @@ describe('getMonitorStatus', () => { }, }; await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -476,7 +474,7 @@ describe('getMonitorStatus', () => { }); it('properly assigns filters for complex kuery filters object', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [{ bucketCriteria: [] }], genBucketItem ); @@ -498,12 +496,12 @@ describe('getMonitorStatus', () => { }, }; await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -581,7 +579,7 @@ describe('getMonitorStatus', () => { }); it('fetches single page of results', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [ { bucketCriteria: [ @@ -618,13 +616,12 @@ describe('getMonitorStatus', () => { }, }; const result = await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -792,12 +789,12 @@ describe('getMonitorStatus', () => { ], }, ]; - const [callES] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( criteria, genBucketItem ); const result = await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, locations: [], numTimes: 5, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts index ac940ffb6676f0..86e5f2876ca28b 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts @@ -6,6 +6,7 @@ import { getPingHistogram } from '../get_ping_histogram'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getPingHistogram', () => { const standardMockResponse: any = { @@ -37,25 +38,28 @@ describe('getPingHistogram', () => { it.skip('returns a single bucket if array has 1', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue({ - aggregations: { - timeseries: { - buckets: [ - { - key: 1, - up: { - doc_count: 2, + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce({ + body: { + aggregations: { + timeseries: { + buckets: [ + { + key: 1, + up: { + doc_count: 2, + }, + down: { + doc_count: 1, + }, }, - down: { - doc_count: 1, - }, - }, - ], - interval: '10s', + ], + interval: '10s', + }, }, }, - }); + } as any); const result = await getPingHistogram({ callES: mockEsClient, @@ -64,16 +68,20 @@ describe('getPingHistogram', () => { to: 'now', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); it('returns expected result for no status filter', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); + + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); standardMockResponse.aggregations.timeseries.interval = '1m'; - mockEsClient.mockReturnValue(standardMockResponse); + + mockEsClient.search.mockResolvedValueOnce({ + body: standardMockResponse, + } as any); const result = await getPingHistogram({ callES: mockEsClient, @@ -83,50 +91,53 @@ describe('getPingHistogram', () => { filters: '', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); it('handles status + additional user queries', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); - - mockEsClient.mockReturnValue({ - aggregations: { - timeseries: { - buckets: [ - { - key: 1, - up: { - doc_count: 2, - }, - down: { - doc_count: 1, - }, - }, - { - key: 2, - up: { - doc_count: 2, - }, - down: { - doc_count: 2, + + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce({ + body: { + aggregations: { + timeseries: { + buckets: [ + { + key: 1, + up: { + doc_count: 2, + }, + down: { + doc_count: 1, + }, }, - }, - { - key: 3, - up: { - doc_count: 3, + { + key: 2, + up: { + doc_count: 2, + }, + down: { + doc_count: 2, + }, }, - down: { - doc_count: 1, + { + key: 3, + up: { + doc_count: 3, + }, + down: { + doc_count: 1, + }, }, - }, - ], - interval: '1h', + ], + interval: '1h', + }, }, }, - }); + } as any); const searchFilter = { bool: { @@ -146,50 +157,52 @@ describe('getPingHistogram', () => { monitorId: undefined, }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); it('handles simple_text_query without issues', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); - - mockEsClient.mockReturnValue({ - aggregations: { - timeseries: { - buckets: [ - { - key: 1, - up: { - doc_count: 2, + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce({ + body: { + aggregations: { + timeseries: { + buckets: [ + { + key: 1, + up: { + doc_count: 2, + }, + down: { + doc_count: 1, + }, }, - down: { - doc_count: 1, + { + key: 2, + up: { + doc_count: 1, + }, + down: { + doc_count: 2, + }, }, - }, - { - key: 2, - up: { - doc_count: 1, - }, - down: { - doc_count: 2, + { + key: 3, + up: { + doc_count: 3, + }, + down: { + doc_count: 1, + }, }, - }, - { - key: 3, - up: { - doc_count: 3, - }, - down: { - doc_count: 1, - }, - }, - ], - interval: '1m', + ], + interval: '1m', + }, }, }, - }); + } as any); const filters = `{"bool":{"must":[{"simple_query_string":{"query":"http"}}]}}`; const result = await getPingHistogram({ @@ -200,7 +213,7 @@ describe('getPingHistogram', () => { filters, }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts index cb84cc2eb05b6e..f313cce9f758bb 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts @@ -7,6 +7,7 @@ import { getPings } from '../get_pings'; import { set } from '@elastic/safer-lodash-set'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getAll', () => { let mockEsSearchResult: any; @@ -49,15 +50,17 @@ describe('getAll', () => { }, ]; mockEsSearchResult = { - hits: { - total: { - value: mockHits.length, + body: { + hits: { + total: { + value: mockHits.length, + }, + hits: mockHits, }, - hits: mockHits, - }, - aggregations: { - locations: { - buckets: [{ key: 'foo' }], + aggregations: { + locations: { + buckets: [{ key: 'foo' }], + }, }, }, }; @@ -84,8 +87,9 @@ describe('getAll', () => { }); it('returns data in the appropriate shape', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); const result = await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -102,12 +106,12 @@ describe('getAll', () => { expect(pings[0].timestamp).toBe('2018-10-30T18:51:59.792Z'); expect(pings[1].timestamp).toBe('2018-10-30T18:53:59.792Z'); expect(pings[2].timestamp).toBe('2018-10-30T18:55:59.792Z'); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); }); it('creates appropriate sort and size parameters', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -117,10 +121,9 @@ describe('getAll', () => { }); set(expectedGetAllParams, 'body.sort[0]', { timestamp: { order: 'asc' } }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -186,8 +189,8 @@ describe('getAll', () => { }); it('omits the sort param when no sort passed', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -195,10 +198,9 @@ describe('getAll', () => { size: 12, }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -264,8 +266,8 @@ describe('getAll', () => { }); it('omits the size param when no size passed', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -273,10 +275,9 @@ describe('getAll', () => { sort: 'desc', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -342,8 +343,8 @@ describe('getAll', () => { }); it('adds a filter for monitor ID', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -351,10 +352,9 @@ describe('getAll', () => { monitorId: 'testmonitorid', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -425,8 +425,8 @@ describe('getAll', () => { }); it('adds a filter for monitor status', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -434,10 +434,9 @@ describe('getAll', () => { status: 'down', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts index 878569b5d390f0..4ebc9b2da78558 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyScopedClusterClient } from 'src/core/server'; import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ElasticsearchClientMock } from '../../../../../../../src/core/server/elasticsearch/client/mocks'; + export interface MultiPageCriteria { after_key?: K; bucketCriteria: T[]; } -export type MockCallES = (method: any, params: any) => Promise; - /** * This utility function will set up a mock ES client, and store subsequent calls. It is designed * to let callers easily simulate an arbitrary series of chained composite aggregation calls by supplying @@ -30,8 +30,8 @@ export type MockCallES = (method: any, params: any) => Promise; export const setupMockEsCompositeQuery = ( criteria: Array>, genBucketItem: (criteria: C) => I -): [MockCallES, jest.Mocked>] => { - const esMock = elasticsearchServiceMock.createLegacyScopedClusterClient(); +): ElasticsearchClientMock => { + const esMock = elasticsearchServiceMock.createElasticsearchClient(); // eslint-disable-next-line @typescript-eslint/naming-convention criteria.forEach(({ after_key, bucketCriteria }) => { @@ -43,8 +43,14 @@ export const setupMockEsCompositeQuery = ( }, }, }; - esMock.callAsCurrentUser.mockResolvedValueOnce(mockResponse); + esMock.search.mockResolvedValueOnce({ + body: mockResponse, + statusCode: 200, + headers: {}, + warnings: [], + meta: {} as any, + }); }); - return [(method: any, params: any) => esMock.callAsCurrentUser(method, params), esMock]; + return esMock; }; diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json b/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json index c62e862a9af89a..9fbfdb98d7fa44 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json @@ -1,146 +1,318 @@ { - "took": 40, - "timed_out": false, - "_shards": { - "total": 1, - "successful": 1, - "skipped": 0, - "failed": 0 - }, - "aggregations": { - "timeseries": { - "buckets": [ - { - "key": 1568411568000, - "doc_count": 4, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 4658759 } }, - { "key": "us-west-4", "duration": { "avg": 8678399.5 } } - ] + "body": { + "took": 40, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "aggregations": { + "timeseries": { + "buckets": [ + { + "key": 1568411568000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 4658759 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 8678399.5 + } + } + ] + } + }, + { + "key": 1568411604000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411640000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 481780 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 685056.5 + } + } + ] + } + }, + { + "key": 1568411784000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 469206.5 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 261406.5 + } + } + ] + } + }, + { + "key": 1568411820000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411856000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411892000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411928000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1999309.6666667 + } + }, + { + "key": "us-east-2", + "duration": { + "avg": 645563 + } + } + ] + } + }, + { + "key": 1568411964000, + "doc_count": 7, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 2499799.25 + } + }, + { + "key": "us-east-2", + "duration": { + "avg": 1513896.6666667 + } + } + ] + } + }, + { + "key": 1568412036000, + "doc_count": 5, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1876155.3333333 + } + }, + { + "key": "us-east-2", + "duration": { + "avg": 1511409 + } + } + ] + } + }, + { + "key": 1568412072000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1490845.75 + } + } + ] + } + }, + { + "key": 1568412108000, + "doc_count": 3, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 2365962.6666667 + } + } + ] + } + }, + { + "key": 1568412144000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1788901.25 + } + } + ] + } + }, + { + "key": 1568412180000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1773177.5 + } + } + ] + } + }, + { + "key": 1568412216000, + "doc_count": 3, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 3086220.3333333 + } + } + ] + } + }, + { + "key": 1568412252000, + "doc_count": 1, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1020528 + } + } + ] + } + }, + { + "key": 1568412288000, + "doc_count": 3, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1643963.3333333 + } + } + ] + } + }, + { + "key": 1568412324000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 1804116 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 1799630 + } + } + ] + } + }, + { + "key": 1568412432000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 1972483.25 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 1543307.5 + } + } + ] + } + }, + { + "key": 1568412468000, + "doc_count": 1, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 1020490 + } + } + ] + } } - }, - { "key": 1568411604000, "doc_count": 0, "location": { "buckets": [] } }, - { - "key": 1568411640000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 481780 } }, - { "key": "us-west-4", "duration": { "avg": 685056.5 } } - ] - } - }, - { - "key": 1568411784000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 469206.5 } }, - { "key": "us-west-4", "duration": { "avg": 261406.5 } } - ] - } - }, - { "key": 1568411820000, "doc_count": 0, "location": { "buckets": [] } }, - { "key": 1568411856000, "doc_count": 0, "location": { "buckets": [] } }, - { "key": 1568411892000, "doc_count": 0, "location": { "buckets": [] } }, - { - "key": 1568411928000, - "doc_count": 4, - "location": { - "buckets": [ - { "key": "us-west-4", "duration": { "avg": 1999309.6666667 } }, - { "key": "us-east-2", "duration": { "avg": 645563 } } - ] - } - }, - { - "key": 1568411964000, - "doc_count": 7, - "location": { - "buckets": [ - { "key": "us-west-4", "duration": { "avg": 2499799.25 } }, - { "key": "us-east-2", "duration": { "avg": 1513896.6666667 } } - ] - } - }, - { - "key": 1568412036000, - "doc_count": 5, - "location": { - "buckets": [ - { "key": "us-west-4", "duration": { "avg": 1876155.3333333 } }, - { "key": "us-east-2", "duration": { "avg": 1511409 } } - ] - } - }, - { - "key": 1568412072000, - "doc_count": 4, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1490845.75 } }] } - }, - { - "key": 1568412108000, - "doc_count": 3, - "location": { - "buckets": [{ "key": "us-west-4", "duration": { "avg": 2365962.6666667 } }] - } - }, - { - "key": 1568412144000, - "doc_count": 4, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1788901.25 } }] } - }, - { - "key": 1568412180000, - "doc_count": 4, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1773177.5 } }] } - }, - { - "key": 1568412216000, - "doc_count": 3, - "location": { - "buckets": [{ "key": "us-west-4", "duration": { "avg": 3086220.3333333 } }] - } - }, - { - "key": 1568412252000, - "doc_count": 1, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1020528 } }] } - }, - { - "key": 1568412288000, - "doc_count": 3, - "location": { - "buckets": [{ "key": "us-west-4", "duration": { "avg": 1643963.3333333 } }] - } - }, - { - "key": 1568412324000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 1804116 } }, - { "key": "us-west-4", "duration": { "avg": 1799630 } } - ] - } - }, - { - "key": 1568412432000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 1972483.25 } }, - { "key": "us-west-4", "duration": { "avg": 1543307.5 } } - ] - } - }, - { - "key": 1568412468000, - "doc_count": 1, - "location": { "buckets": [{ "key": "us-east-2", "duration": { "avg": 1020490 } }] } - } - ] + ] + } } } } diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 4793d420cbfd83..0836cb039b215e 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -145,7 +145,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn } // console.log(JSON.stringify(params, null, 2)); - const result = await callES('search', params); + const { body: result } = await callES.search(params); const certs = (result?.hits?.hits ?? []).map((hit: any) => { const { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts b/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts index e89b457eccf32d..c3295d6dd9c8f7 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts @@ -93,6 +93,8 @@ export const getFilterBar: UMElasticsearchQueryFn = async ({ esClient, dynamicSettings }) => { +export const getUptimeIndexPattern = async ({ + esClient, + dynamicSettings, +}: { + esClient: ElasticsearchClient; + dynamicSettings: DynamicSettings; +}): Promise => { const indexPatternsFetcher = new IndexPatternsFetcher(esClient); // Since `getDynamicIndexPattern` is called in setup_request (and thus by every endpoint) @@ -28,12 +31,10 @@ export const getUptimeIndexPattern: UMElasticsearchQueryFn< pattern: dynamicSettings.heartbeatIndices, }); - const indexPattern: IndexPatternTitleAndFields = { + return { fields, title: dynamicSettings.heartbeatIndices, }; - - return indexPattern; } catch (e) { const notExists = e.output?.statusCode === 404; if (notExists) { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts index 7688f04f1acd95..061d002b010de4 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts @@ -12,9 +12,11 @@ export const getIndexStatus: UMElasticsearchQueryFn<{}, StatesIndexStatus> = asy dynamicSettings, }) => { const { - _shards: { total }, - count, - } = await callES('count', { index: dynamicSettings.heartbeatIndices }); + body: { + _shards: { total }, + count, + }, + } = await callES.count({ index: dynamicSettings.heartbeatIndices }); return { indexExists: total > 0, docCount: count, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts index f726ef47915b89..bff3aaf1176df3 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts @@ -42,7 +42,7 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn< _source: ['synthetics.blob'], }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); if (!Array.isArray(result?.hits?.hits) || result.hits.hits.length < 1) { return null; } diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts index 9c139b2ce85886..f36815a747db3d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts @@ -42,7 +42,7 @@ export const getJourneySteps: UMElasticsearchQueryFn h?._source?.synthetics?.type === 'step/screenshot') .map((h: any) => h?._source?.synthetics?.step?.index); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts index d32b78bdc71394..f6562eaa42e900 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts @@ -57,7 +57,7 @@ export const getLatestMonitor: UMElasticsearchQueryFn { +const getMonitorAlerts = async ({ + callES, + dynamicSettings, + alertsClient, + monitorId, +}: { + callES: ElasticsearchClient; + dynamicSettings: any; + alertsClient: any; + monitorId: string; +}) => { const options: any = { page: 1, perPage: 500, @@ -70,13 +73,12 @@ const getMonitorAlerts = async ( const parsedFilters = await formatFilterString( dynamicSettings, callES, - esClient, currAlert.params.filters, currAlert.params.search ); esParams.body.query.bool = Object.assign({}, esParams.body.query.bool, parsedFilters?.bool); - const result = await callES('search', esParams); + const { body: result } = await callES.search(esParams); if (result.hits.total.value > 0) { monitorAlerts.push(currAlert); @@ -88,7 +90,7 @@ const getMonitorAlerts = async ( export const getMonitorDetails: UMElasticsearchQueryFn< GetMonitorDetailsParams, MonitorDetails -> = async ({ callES, esClient, dynamicSettings, monitorId, dateStart, dateEnd, alertsClient }) => { +> = async ({ callES, dynamicSettings, monitorId, dateStart, dateEnd, alertsClient }) => { const queryFilters: any = [ { range: { @@ -132,19 +134,19 @@ export const getMonitorDetails: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const data = result.hits.hits[0]?._source; const monitorError: MonitorError | undefined = data?.error; const errorTimestamp: string | undefined = data?.['@timestamp']; - const monAlerts = await getMonitorAlerts( + const monAlerts = await getMonitorAlerts({ callES, - esClient, dynamicSettings, alertsClient, - monitorId - ); + monitorId, + }); + return { monitorId, error: monitorError, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts index 00ca1b58783297..77ae7570a96a84 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts @@ -59,7 +59,7 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const dateHistogramBuckets: any[] = result?.aggregations?.timeseries?.buckets ?? []; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts index f52e965d488ea9..b5183ca9ffb9fc 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts @@ -88,7 +88,7 @@ export const getMonitorLocations: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const locations = result?.aggregations?.location?.buckets ?? []; const getGeo = (locGeo: { name: string; location?: string }) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts index 3e49a32881f542..020fcf5331188d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts @@ -153,7 +153,7 @@ export const getHistogramForMonitors = async ( }; const result = await queryContext.search(params); - const histoBuckets: any[] = result.aggregations.histogram.buckets; + const histoBuckets: any[] = result.aggregations?.histogram.buckets ?? []; const simplified = histoBuckets.map((histoBucket: any): { timestamp: number; byId: any } => { const byId: { [key: string]: number } = {}; histoBucket.by_id.buckets.forEach((idBucket: any) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts index caf505610e991b..06648d68969c14 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts @@ -133,7 +133,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn< esParams.body.aggs.monitors.composite.after = afterKey; } - const result = await callES('search', esParams); + const { body: result } = await callES.search(esParams); afterKey = result?.aggregations?.monitors?.after_key; monitors = monitors.concat(result?.aggregations?.monitors?.buckets || []); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts index 5d8706e2fc5f17..4eb2d862cb7023 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts @@ -76,7 +76,7 @@ export const getPingHistogram: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const buckets: HistogramQueryResult[] = result?.aggregations?.timeseries?.buckets ?? []; const histogram = buckets.map((bucket) => { const x: number = bucket.key; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts index 03ec2d7343c9ab..e72b16de3d66f4 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts @@ -108,9 +108,11 @@ export const getPings: UMElasticsearchQueryFn = a } const { - hits: { hits, total }, - aggregations: aggs, - } = await callES('search', params); + body: { + hits: { hits, total }, + aggregations: aggs, + }, + } = await callES.search(params); const locations = aggs?.locations ?? { buckets: [{ key: 'N/A', doc_count: 0 }] }; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts index 92295a38cffb4f..ac36585ff09397 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts @@ -39,7 +39,7 @@ export const getSnapshotCount: UMElasticsearchQueryFn => { - const res = await context.search({ + const { body: res } = await context.search({ index: context.heartbeatIndices, body: statusCountBody(await context.dateAndCustomFilters()), }); diff --git a/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts index 6c229cf30e165f..38e7dabb19941b 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts @@ -19,7 +19,7 @@ export const findPotentialMatches = async ( searchAfter: any, size: number ) => { - const queryResult = await query(queryContext, searchAfter, size); + const { body: queryResult } = await query(queryContext, searchAfter, size); const monitorIds: string[] = []; get(queryResult, 'aggregations.monitors.buckets', []).forEach((b: any) => { const monitorId = b.key.monitor_id; diff --git a/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts index 5d97e635f3e7d7..96df8ea651c44a 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts @@ -5,13 +5,13 @@ */ import moment from 'moment'; -import { LegacyAPICaller } from 'src/core/server'; +import { ElasticsearchClient } from 'kibana/server'; import { CursorPagination } from './types'; import { parseRelativeDate } from '../../helper'; import { CursorDirection, SortOrder } from '../../../../common/runtime_types'; export class QueryContext { - callES: LegacyAPICaller; + callES: ElasticsearchClient; heartbeatIndices: string; dateRangeStart: string; dateRangeEnd: string; @@ -43,12 +43,12 @@ export class QueryContext { async search(params: any): Promise { params.index = this.heartbeatIndices; - return this.callES('search', params); + return this.callES.search(params); } async count(params: any): Promise { params.index = this.heartbeatIndices; - return this.callES('count', params); + return this.callES.count(params); } async dateAndCustomFilters(): Promise { diff --git a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts index a864bfa591424e..6be9f813016f80 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts @@ -23,7 +23,7 @@ export const refinePotentialMatches = async ( return []; } - const queryResult = await query(queryContext, potentialMatchMonitorIDs); + const { body: queryResult } = await query(queryContext, potentialMatchMonitorIDs); return await fullyMatchingIds(queryResult, queryContext.statusFilter); }; 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 baf999158a29e4..418cde9e701d50 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 @@ -17,8 +17,7 @@ export const createGetIndexPatternRoute: UMRestApiRouteFactory = (libs: UMServer return response.ok({ body: { ...(await libs.requests.getIndexPattern({ - callES, - esClient: _context.core.elasticsearch.client.asCurrentUser, + esClient: callES, dynamicSettings, })), }, 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 0e2c8c180e0e05..7b461060bf4bce 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 @@ -25,44 +25,51 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({ tags: ['access:uptime-read'], }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { - const { - dateRangeStart, - dateRangeEnd, - filters, - pagination, - statusFilter, - pageSize, - } = request.query; - - const decodedPagination = pagination - ? JSON.parse(decodeURIComponent(pagination)) - : CONTEXT_DEFAULTS.CURSOR_PAGINATION; - const [indexStatus, { summaries, nextPagePagination, prevPagePagination }] = await Promise.all([ - libs.requests.getIndexStatus({ callES, dynamicSettings }), - libs.requests.getMonitorStates({ - callES, - dynamicSettings, + try { + const { 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, - }), - ]); + pagination, + statusFilter, + pageSize, + } = request.query; + + const decodedPagination = pagination + ? JSON.parse(decodeURIComponent(pagination)) + : CONTEXT_DEFAULTS.CURSOR_PAGINATION; + const [ + indexStatus, + { summaries, nextPagePagination, prevPagePagination }, + ] = await Promise.all([ + libs.requests.getIndexStatus({ callES, dynamicSettings }), + libs.requests.getMonitorStates({ + callES, + dynamicSettings, + 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 totalSummaryCount = indexStatus?.docCount ?? 0; + const totalSummaryCount = indexStatus?.docCount ?? 0; - return response.ok({ - body: { - summaries, - nextPagePagination, - prevPagePagination, - totalSummaryCount, - }, - }); + return response.ok({ + body: { + summaries, + nextPagePagination, + prevPagePagination, + totalSummaryCount, + }, + }); + } catch (e) { + return response.internalError({ body: { message: e.message } }); + } }, }); 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 8bbb4fcb5575c2..bb54effc0d57e8 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 @@ -28,7 +28,6 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ body: { ...(await libs.requests.getMonitorDetails({ callES, - esClient: context.core.elasticsearch.client.asCurrentUser, dynamicSettings, monitorId, dateStart, diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts index 589cb82d550f67..5e5f4a2a991cfd 100644 --- a/x-pack/plugins/uptime/server/rest_api/types.ts +++ b/x-pack/plugins/uptime/server/rest_api/types.ts @@ -9,12 +9,13 @@ import { RequestHandler, RouteConfig, RouteMethod, - LegacyCallAPIOptions, SavedObjectsClientContract, RequestHandlerContext, KibanaRequest, KibanaResponseFactory, IKibanaResponse, + IScopedClusterClient, + ElasticsearchClient, } from 'kibana/server'; import { DynamicSettings } from '../../common/runtime_types'; import { UMServerLibs } from '../lib/lib'; @@ -63,11 +64,8 @@ export type UMKibanaRouteWrapper = (uptimeRoute: UptimeRoute) => UMKibanaRoute; * This type can store custom parameters used by the internal Uptime route handlers. */ export interface UMRouteParams { - callES: ( - endpoint: string, - clientParams?: Record, - options?: LegacyCallAPIOptions | undefined - ) => Promise; + callES: ElasticsearchClient; + esClient: IScopedClusterClient; dynamicSettings: DynamicSettings; savedObjectsClient: SavedObjectsClientContract; } 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 84a85a54afe138..b2f1c7d6424e62 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 @@ -13,11 +13,11 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], }, handler: async (context, request, response) => { - const { callAsCurrentUser: callES } = context.core.elasticsearch.legacy.client; + const { client: esClient } = context.core.elasticsearch; const { client: savedObjectsClient } = context.core.savedObjects; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); return uptimeRoute.handler( - { callES, savedObjectsClient, dynamicSettings }, + { callES: esClient.asCurrentUser, esClient, savedObjectsClient, dynamicSettings }, context, request, response From 98b46c91bd5493833e7428018c5a6ab9526c7284 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 6 Nov 2020 13:56:59 -0500 Subject: [PATCH 38/81] Update webpack-dev-server and webpack-cli (#82844) --- package.json | 4 +- yarn.lock | 209 +++++++++++++++++---------------------------------- 2 files changed, 72 insertions(+), 141 deletions(-) diff --git a/package.json b/package.json index 821975d11c6388..ade567c840da77 100644 --- a/package.json +++ b/package.json @@ -844,8 +844,8 @@ "vinyl-fs": "^3.0.3", "wait-on": "^5.0.1", "watchpack": "^1.6.0", - "webpack-cli": "^3.3.10", - "webpack-dev-server": "^3.8.2", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2", "write-pkg": "^4.0.0", "xml-crypto": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 833e8bffcfc806..0b429c96c18479 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10370,17 +10370,6 @@ cross-fetch@2.2.2: node-fetch "2.1.2" whatwg-fetch "2.0.4" -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@7.0.1, cross-spawn@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -10398,6 +10387,17 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -12245,16 +12245,7 @@ endent@^2.0.1: fast-json-parse "^1.0.3" objectorarray "^1.0.4" -enhanced-resolve@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - -enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0, enhanced-resolve@^4.3.0: +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== @@ -13749,16 +13740,6 @@ find@^0.3.0: dependencies: traverse-chain "~0.1.0" -findup-sync@3.0.0, findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -13769,6 +13750,16 @@ findup-sync@^2.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + findup-sync@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16" @@ -14637,7 +14628,7 @@ global-dirs@^2.0.1: dependencies: ini "^1.3.5" -global-modules@2.0.0: +global-modules@2.0.0, global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== @@ -16195,7 +16186,7 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= -import-local@2.0.0, import-local@^2.0.0: +import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== @@ -16430,10 +16421,10 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.2" -interpret@1.2.0, interpret@^1.0.0, interpret@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== +interpret@^1.0.0, interpret@^1.1.0, interpret@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== interpret@^2.0.0: version "2.2.0" @@ -16481,11 +16472,6 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - io-ts@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.5.tgz#e6e3db9df8b047f9cbd6b69e7d2ad3e6437a0b13" @@ -18624,13 +18610,6 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - lead@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" @@ -18916,6 +18895,15 @@ loader-utils@2.0.0, loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -19449,13 +19437,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -map-age-cleaner@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74" - integrity sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -19689,15 +19670,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf" - integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^1.0.0" - p-is-promise "^1.1.0" - "memoize-one@>=3.1.1 <6", memoize-one@^5.0.0, memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -19729,7 +19701,7 @@ memory-fs@^0.2.0: resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" integrity sha1-8rslNovBIeORwlIN6Slpyu4KApA= -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -20620,11 +20592,6 @@ node-fetch@2.1.2, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== - node-forge@^0.10.0, node-forge@^0.7.6: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -21408,15 +21375,6 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-locale@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-name@^3.0.0, os-name@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -21477,11 +21435,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -21506,11 +21459,6 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -25133,11 +25081,11 @@ selenium-webdriver@^4.0.0-alpha.7: tmp "0.0.30" selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== dependencies: - node-forge "0.9.0" + node-forge "^0.10.0" semver-diff@^2.0.0: version "2.1.0" @@ -26572,13 +26520,6 @@ supports-color@6.0.0: dependencies: has-flag "^3.0.0" -supports-color@6.1.0, supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -26596,6 +26537,13 @@ supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-co dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.0.0, supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -28406,10 +28354,10 @@ uuid@^8.0.0, uuid@^8.3.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== -v8-compile-cache@2.0.3, v8-compile-cache@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== +v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== v8-to-istanbul@^5.0.1: version "5.0.1" @@ -29156,22 +29104,22 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-cli@^3.3.10: - version "3.3.10" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.10.tgz#17b279267e9b4fb549023fae170da8e6e766da13" - integrity sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg== +webpack-cli@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" + integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" + chalk "^2.4.2" + cross-spawn "^6.0.5" + enhanced-resolve "^4.1.1" + findup-sync "^3.0.0" + global-modules "^2.0.0" + import-local "^2.0.0" + interpret "^1.4.0" + loader-utils "^1.4.0" + supports-color "^6.1.0" + v8-compile-cache "^2.1.1" + yargs "^13.3.2" webpack-dev-middleware@^3.7.0, webpack-dev-middleware@^3.7.2: version "3.7.2" @@ -29184,7 +29132,7 @@ webpack-dev-middleware@^3.7.0, webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.8.2: +webpack-dev-server@^3.11.0: version "3.11.0" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== @@ -29806,7 +29754,7 @@ yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== -yargs-parser@13.1.2, yargs-parser@^13.1.0, yargs-parser@^13.1.2: +yargs-parser@13.1.2, yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== @@ -29844,23 +29792,6 @@ yargs-unparser@1.6.0: lodash "^4.17.15" yargs "^13.3.0" -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - os-locale "^3.1.0" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.0" - yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" From 715d43be35cbb02748018ee9437dad5359b3459f Mon Sep 17 00:00:00 2001 From: John Schulz Date: Fri, 6 Nov 2020 15:15:22 -0500 Subject: [PATCH 39/81] [Ingest Manager] Move cache functions to from registry to archive (#82871) ## Summary Moving the memory store functions to `archive/cache.ts` to better express their role. They are archive-related functions. The registry is just one possible source of an archive/assets. Also considered moving to `assets`, but can always come back to that. `deletePackageCache` undoes side-effects from `unpackArchiveToCache` so put them in the same file --- .../epm/{registry => archive}/cache.ts | 2 +- .../server/services/epm/archive/index.ts | 22 ++++++++++++++++++- .../server/services/epm/archive/validation.ts | 2 +- .../services/epm/packages/assets.test.ts | 4 ++-- .../server/services/epm/packages/assets.ts | 2 +- .../server/services/epm/packages/remove.ts | 3 ++- .../server/services/epm/registry/index.ts | 20 +---------------- 7 files changed, 29 insertions(+), 26 deletions(-) rename x-pack/plugins/ingest_manager/server/services/epm/{registry => archive}/cache.ts (95%) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts similarity index 95% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts rename to x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts index 695db9db73fa25..102324c18bd430 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { pkgToPkgKey } from './index'; +import { pkgToPkgKey } from '../registry/index'; const cache: Map = new Map(); export const cacheGet = (key: string) => cache.get(key); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts index ee505b205fc847..27451ed6b5e609 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts @@ -6,10 +6,18 @@ import { ArchivePackage } from '../../../../common/types'; import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors'; -import { cacheSet, setArchiveFilelist } from '../registry/cache'; +import { + cacheSet, + cacheDelete, + getArchiveFilelist, + setArchiveFilelist, + deleteArchiveFilelist, +} from './cache'; import { ArchiveEntry, getBufferExtractor } from '../registry/extract'; import { parseAndVerifyArchive } from './validation'; +export * from './cache'; + export async function loadArchivePackage({ archiveBuffer, contentType, @@ -64,3 +72,15 @@ export async function unpackArchiveToCache( } return paths; } + +export const deletePackageCache = (name: string, version: string) => { + // get cached archive filelist + const paths = getArchiveFilelist(name, version); + + // delete cached archive filelist + deleteArchiveFilelist(name, version); + + // delete cached archive files + // this has been populated in unpackArchiveToCache() + paths?.forEach((path) => cacheDelete(path)); +}; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts index e83340124a2d03..90941aaf80cddc 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts @@ -16,7 +16,7 @@ import { } from '../../../../common/types'; import { PackageInvalidArchiveError } from '../../../errors'; import { pkgToPkgKey } from '../registry'; -import { cacheGet } from '../registry/cache'; +import { cacheGet } from './cache'; // TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the // package registry. At some point this should probably be replaced (or enhanced) with verification based on diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts index eb43bef72db703..ab93a73a55f39a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts @@ -6,9 +6,9 @@ import { InstallablePackage } from '../../../types'; import { getAssets } from './assets'; -import { getArchiveFilelist } from '../registry/cache'; +import { getArchiveFilelist } from '../archive/cache'; -jest.mock('../registry/cache', () => { +jest.mock('../archive/cache', () => { return { getArchiveFilelist: jest.fn(), }; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index 856f04c0c9b67b..2e2090312c9ae4 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -6,7 +6,7 @@ import { InstallablePackage } from '../../../types'; import * as Registry from '../registry'; -import { getArchiveFilelist } from '../registry/cache'; +import { getArchiveFilelist } from '../archive/cache'; // paths from RegistryPackage are routes to the assets on EPR // e.g. `/package/nginx/1.2.0/data_stream/access/fields/fields.yml` diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index 5db47adc983c2a..9fabbaf72474e2 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -21,7 +21,8 @@ import { deletePipeline } from '../elasticsearch/ingest_pipeline/'; import { installIndexPatterns } from '../kibana/index_pattern/install'; import { deleteTransforms } from '../elasticsearch/transform/remove'; import { packagePolicyService, appContextService } from '../..'; -import { splitPkgKey, deletePackageCache } from '../registry'; +import { splitPkgKey } from '../registry'; +import { deletePackageCache } from '../archive'; export async function removeInstallation(options: { savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index b1dd9a8c3c3f19..013315eb6da235 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -19,13 +19,7 @@ import { RegistrySearchResult, } from '../../../types'; import { unpackArchiveToCache } from '../archive'; -import { - cacheGet, - cacheDelete, - getArchiveFilelist, - setArchiveFilelist, - deleteArchiveFilelist, -} from './cache'; +import { cacheGet, getArchiveFilelist, setArchiveFilelist } from '../archive'; import { ArchiveEntry } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; @@ -247,15 +241,3 @@ export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByTy // elasticsearch: assets.elasticsearch, }; } - -export const deletePackageCache = (name: string, version: string) => { - // get cached archive filelist - const paths = getArchiveFilelist(name, version); - - // delete cached archive filelist - deleteArchiveFilelist(name, version); - - // delete cached archive files - // this has been populated in unpackRegistryPackageToCache() - paths?.forEach((path) => cacheDelete(path)); -}; From fb9d39500aca9033a6e04cfe565b63197df2b715 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Fri, 6 Nov 2020 12:48:21 -0800 Subject: [PATCH 40/81] [APM] Fix apm e2e runner script commands (#82798) * Fixes apm e2e runner script by updating the yarn commands to match the script names defined in the package.json * removes the yarn install step for cypress dependencies, since it's no longer necessary. * Removed apm/e2e/package.json * simplified paths for binary dependencies --- .../apm/e2e/cypress/integration/snapshots.js | 4 ++-- x-pack/plugins/apm/e2e/package.json | 10 ---------- x-pack/plugins/apm/e2e/run-e2e.sh | 16 +++++----------- 3 files changed, 7 insertions(+), 23 deletions(-) delete mode 100644 x-pack/plugins/apm/e2e/package.json diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js index 72b49bb85b7a59..0ecda7a113de75 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.5.0', -}; + "__version": "5.4.0" +} diff --git a/x-pack/plugins/apm/e2e/package.json b/x-pack/plugins/apm/e2e/package.json deleted file mode 100644 index 5839f4d58537c0..00000000000000 --- a/x-pack/plugins/apm/e2e/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "apm-cypress", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "cypress:open": "../../../../node_modules/.bin/cypress open", - "cypress:run": "../../../../node_modules/.bin/cypress run --spec **/*.feature" - } -} \ No newline at end of file diff --git a/x-pack/plugins/apm/e2e/run-e2e.sh b/x-pack/plugins/apm/e2e/run-e2e.sh index 6cdae93aec63be..85ab67bbf9a10d 100755 --- a/x-pack/plugins/apm/e2e/run-e2e.sh +++ b/x-pack/plugins/apm/e2e/run-e2e.sh @@ -20,6 +20,8 @@ normal=$(tput sgr0) E2E_DIR="${0%/*}" TMP_DIR="tmp" APM_IT_DIR="tmp/apm-integration-testing" +WAIT_ON_BIN="../../../../node_modules/.bin/wait-on" +CYPRESS_BIN="../../../../node_modules/.bin/cypress" cd ${E2E_DIR} @@ -92,14 +94,6 @@ if [ $? -ne 0 ]; then exit 1 fi -# -# Cypress -################################################## -echo "" # newline -echo "${bold}Cypress (logs: ${E2E_DIR}${TMP_DIR}/e2e-yarn.log)${normal}" -echo "Installing cypress dependencies " -yarn &> ${TMP_DIR}/e2e-yarn.log - # # Static mock data ################################################## @@ -148,7 +142,7 @@ fi echo "" # newline echo "${bold}Waiting for Kibana to start...${normal}" echo "Note: you need to start Kibana manually. Find the instructions at the top." -yarn wait-on -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null +$WAIT_ON_BIN -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null ## Workaround to wait for the http server running ## See: https://github.com/elastic/kibana/issues/66326 @@ -165,7 +159,7 @@ echo "✅ Setup completed successfully. Running tests..." # # run cypress tests ################################################## -yarn cypress run --config pageLoadTimeout=100000,watchForFileChanges=true +$CYPRESS_BIN run --config pageLoadTimeout=100000,watchForFileChanges=true e2e_status=$? # @@ -173,7 +167,7 @@ e2e_status=$? ################################################## echo "${bold}If you want to run the test interactively, run:${normal}" echo "" # newline -echo "cd ${E2E_DIR} && yarn cypress open --config pageLoadTimeout=100000,watchForFileChanges=true" +echo "cd ${E2E_DIR} && ${CYPRESS_BIN} open --config pageLoadTimeout=100000,watchForFileChanges=true" # Report the e2e status at the very end if [ $e2e_status -ne 0 ]; then From e53da760d83cd0eb32861b54b6ddfc6bcacd8689 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Fri, 6 Nov 2020 16:04:27 -0500 Subject: [PATCH 41/81] [Security Solution][Detections] - Auto refresh all rules/monitoring tables (#82062) ## Summary This PR addresses https://github.com/elastic/kibana/issues/63865 . Please read the issue for more detail, but essentially, stale data on the tables and use of relative date format leads to confusion as to whether the table was auto refreshing or not. --- .../security_solution/common/constants.ts | 6 + .../alerts_detection_rules.spec.ts | 37 +++ .../cypress/screens/alerts_detection_rules.ts | 10 +- .../cypress/tasks/alerts_detection_rules.ts | 38 ++- .../__snapshots__/index.test.tsx.snap | 4 +- .../components/header_section/index.tsx | 4 +- .../components/last_updated/index.test.tsx | 47 +++ .../common/components/last_updated/index.tsx | 83 +++++ .../components/last_updated/translations.ts | 15 + .../common/lib/kibana/kibana_react.mock.ts | 9 + .../detection_engine/rules/all/columns.tsx | 2 +- .../detection_engine/rules/all/index.test.tsx | 196 +++++++----- .../detection_engine/rules/all/index.tsx | 301 ++++++++++++------ .../rules/all/reducer.test.ts | 269 ++++++++++++++++ .../detection_engine/rules/all/reducer.ts | 66 ++-- .../rules_table_filters.test.tsx | 43 ++- .../rules_table_filters.tsx | 8 +- .../rules/all/utility_bar.test.tsx | 116 +++++++ .../rules/all/utility_bar.tsx | 118 +++++++ .../detection_engine/rules/translations.ts | 35 ++ .../components/timeline/footer/index.tsx | 2 +- .../timeline/footer/last_updated.tsx | 68 ---- .../timeline/footer/translations.ts | 4 - .../security_solution/server/ui_settings.ts | 29 ++ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 26 files changed, 1226 insertions(+), 286 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts create mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 767a2616a4c7e3..8c423c663a4e8c 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -39,6 +39,9 @@ export const FILTERS_GLOBAL_HEIGHT = 109; // px export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled'; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51'; export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*'; +export const DEFAULT_RULE_REFRESH_INTERVAL_ON = true; +export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; // ms +export const DEFAULT_RULE_REFRESH_IDLE_VALUE = 2700000; // ms export enum SecurityPageName { detections = 'detections', @@ -74,6 +77,9 @@ export const DEFAULT_INDEX_PATTERN = [ /** This Kibana Advanced Setting enables the `Security news` feed widget */ export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed'; +/** This Kibana Advanced Setting sets the auto refresh interval for the detections all rules table */ +export const DEFAULT_RULES_TABLE_REFRESH_SETTING = 'securitySolution:rulesTableRefresh'; + /** This Kibana Advanced Setting specifies the URL of the News feed widget */ export const NEWS_FEED_URL_SETTING = 'securitySolution:newsFeedUrl'; diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts index 3fa304ab7cf190..6a62caecfaa675 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts @@ -10,6 +10,7 @@ import { RULE_SWITCH, SECOND_RULE, SEVENTH_RULE, + RULE_AUTO_REFRESH_IDLE_MODAL, } from '../screens/alerts_detection_rules'; import { @@ -19,12 +20,17 @@ import { } from '../tasks/alerts'; import { activateRule, + checkAllRulesIdleModal, + checkAutoRefresh, + dismissAllRulesIdleModal, + resetAllRulesIdleModalTimeout, sortByActivatedRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRuleToBeActivated, } from '../tasks/alerts_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -35,6 +41,7 @@ describe('Alerts detection rules', () => { after(() => { esArchiverUnload('prebuilt_rules_loaded'); + cy.clock().invoke('restore'); }); it('Sorts by activated rules', () => { @@ -75,4 +82,34 @@ describe('Alerts detection rules', () => { }); }); }); + + it('Auto refreshes rules', () => { + cy.clock(Date.now()); + + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertsDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + + // mock 1 minute passing to make sure refresh + // is conducted + checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible'); + + // mock 45 minutes passing to check that idle modal shows + // and refreshing is paused + checkAllRulesIdleModal('be.visible'); + checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'not.be.visible'); + + // clicking on modal to continue, should resume refreshing + dismissAllRulesIdleModal(); + checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible'); + + // if mouse movement detected, idle modal should not + // show after 45 min + resetAllRulesIdleModalTimeout(); + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.exist'); + + cy.clock().invoke('restore'); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 0d0ea8460edf11..5ac8cd8f6cc9f7 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -10,7 +10,7 @@ export const CREATE_NEW_RULE_BTN = '[data-test-subj="create-new-rule"]'; export const COLLAPSED_ACTION_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]'; -export const CUSTOM_RULES_BTN = '[data-test-subj="show-custom-rules-filter-button"]'; +export const CUSTOM_RULES_BTN = '[data-test-subj="showCustomRulesFilterButton"]'; export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]'; @@ -18,7 +18,7 @@ export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]'; export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]'; -export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]'; +export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton"]'; export const EXPORT_ACTION_BTN = '[data-test-subj="exportRuleAction"]'; @@ -31,7 +31,7 @@ export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; export const LOADING_INITIAL_PREBUILT_RULES_TABLE = '[data-test-subj="initialLoadingPanelAllRulesTable"]'; -export const LOADING_SPINNER = '[data-test-subj="loading-spinner"]'; +export const ASYNC_LOADING_PROGRESS = '[data-test-subj="loadingRulesInfoProgress"]'; export const NEXT_BTN = '[data-test-subj="pagination-button-next"]'; @@ -64,3 +64,7 @@ export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]'; export const SORT_RULES_BTN = '[data-test-subj="tableHeaderSortButton"]'; export const THREE_HUNDRED_ROWS = '[data-test-subj="tablePagination-300-rows"]'; + +export const RULE_AUTO_REFRESH_IDLE_MODAL = '[data-test-subj="allRulesIdleModal"]'; + +export const RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE = '[data-test-subj="allRulesIdleModal"] button'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 1c430e12b6b734..d4602dcd16db80 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -13,7 +13,6 @@ import { DELETE_RULE_BULK_BTN, LOAD_PREBUILT_RULES_BTN, LOADING_INITIAL_PREBUILT_RULES_TABLE, - LOADING_SPINNER, PAGINATION_POPOVER_BTN, RELOAD_PREBUILT_RULES_BTN, RULE_CHECKBOX, @@ -26,6 +25,9 @@ import { EXPORT_ACTION_BTN, EDIT_RULE_ACTION_BTN, NEXT_BTN, + ASYNC_LOADING_PROGRESS, + RULE_AUTO_REFRESH_IDLE_MODAL, + RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE, } from '../screens/alerts_detection_rules'; import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details'; @@ -66,8 +68,8 @@ export const exportFirstRule = () => { export const filterByCustomRules = () => { cy.get(CUSTOM_RULES_BTN).click({ force: true }); - cy.get(LOADING_SPINNER).should('exist'); - cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('not.exist'); }; export const goToCreateNewRule = () => { @@ -119,6 +121,32 @@ export const waitForRuleToBeActivated = () => { }; export const waitForRulesToBeLoaded = () => { - cy.get(LOADING_SPINNER).should('exist'); - cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('not.exist'); +}; + +// when using, ensure you've called cy.clock prior in test +export const checkAutoRefresh = (ms: number, condition: string) => { + cy.get(ASYNC_LOADING_PROGRESS).should('not.be.visible'); + cy.tick(ms); + cy.get(ASYNC_LOADING_PROGRESS).should(condition); +}; + +export const dismissAllRulesIdleModal = () => { + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE) + .eq(1) + .should('exist') + .click({ force: true, multiple: true }); + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.be.visible'); +}; + +export const checkAllRulesIdleModal = (condition: string) => { + cy.tick(2700000); + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should(condition); +}; + +export const resetAllRulesIdleModalTimeout = () => { + cy.tick(2000000); + cy.window().trigger('mousemove', { force: true }); + cy.tick(700000); }; diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap index f2d2d23d60fb13..d3d20c71835707 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap @@ -7,7 +7,9 @@ exports[`HeaderSection it renders 1`] = ` - + = ({ @@ -57,10 +58,11 @@ const HeaderSectionComponent: React.FC = ({ title, titleSize = 'm', tooltip, + growLeftSplit = true, }) => (
- + diff --git a/x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx new file mode 100644 index 00000000000000..db42794448c533 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx @@ -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 React from 'react'; +import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { LastUpdatedAt } from './'; + +describe('LastUpdatedAt', () => { + beforeEach(() => { + Date.now = jest.fn().mockReturnValue(1603995369774); + }); + + test('it renders correct relative time', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(' Updated 2 minutes ago'); + }); + + test('it only renders icon if "compact" is true', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(''); + expect(wrapper.find('[data-test-subj="last-updated-at-clock-icon"]').exists()).toBeTruthy(); + }); + + test('it renders updating text if "showUpdating" is true', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(' Updating...'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx b/x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx new file mode 100644 index 00000000000000..ef4ff0123dd1cc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useEffect, useMemo, useState } from 'react'; + +import * as i18n from './translations'; + +interface LastUpdatedAtProps { + compact?: boolean; + updatedAt: number; + showUpdating?: boolean; +} + +export const Updated = React.memo<{ date: number; prefix: string; updatedAt: number }>( + ({ date, prefix, updatedAt }) => ( + <> + {prefix} + { + + } + + ) +); + +Updated.displayName = 'Updated'; + +const prefix = ` ${i18n.UPDATED} `; + +export const LastUpdatedAt = React.memo( + ({ compact = false, updatedAt, showUpdating = false }) => { + const [date, setDate] = useState(Date.now()); + + function tick() { + setDate(Date.now()); + } + + useEffect(() => { + const timerID = setInterval(() => tick(), 10000); + return () => { + clearInterval(timerID); + }; + }, []); + + const updateText = useMemo(() => { + if (showUpdating) { + return {i18n.UPDATING}; + } + + if (!compact) { + return ; + } + + return null; + }, [compact, date, showUpdating, updatedAt]); + + return ( + + + + } + > + + + {updateText} + + + ); + } +); + +LastUpdatedAt.displayName = 'LastUpdatedAt'; diff --git a/x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts b/x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts new file mode 100644 index 00000000000000..77278563b24d5a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const UPDATING = i18n.translate('xpack.securitySolution.lastUpdated.updating', { + defaultMessage: 'Updating...', +}); + +export const UPDATED = i18n.translate('xpack.securitySolution.lastUpdated.updated', { + defaultMessage: 'Updated', +}); diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 06c152b94cfd82..38ae49ba3b19cf 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -27,6 +27,10 @@ import { DEFAULT_REFRESH_RATE_INTERVAL, DEFAULT_TIME_RANGE, DEFAULT_TO, + DEFAULT_RULES_TABLE_REFRESH_SETTING, + DEFAULT_RULE_REFRESH_INTERVAL_ON, + DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + DEFAULT_RULE_REFRESH_IDLE_VALUE, } from '../../../../common/constants'; import { StartServices } from '../../../types'; import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage'; @@ -48,6 +52,11 @@ const mockUiSettings: Record = { [DEFAULT_DATE_FORMAT_TZ]: 'UTC', [DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS', [DEFAULT_DARK_MODE]: false, + [DEFAULT_RULES_TABLE_REFRESH_SETTING]: { + on: DEFAULT_RULE_REFRESH_INTERVAL_ON, + value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE, + }, }; export const createUseUiSettingMock = () => (key: string, defaultValue?: unknown): unknown => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index 6800743db738eb..2b03d6dd4de364 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -210,7 +210,7 @@ export const getColumns = ({ getEmptyTagValue() ) : ( - + ); }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index 1a4c2d405dca32..be42d7b3212fd5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -6,13 +6,21 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; import '../../../../../common/mock/match_media'; import '../../../../../common/mock/formatted_relative'; -import { TestProviders } from '../../../../../common/mock'; -import { waitFor } from '@testing-library/react'; import { AllRules } from './index'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; +import { useRules, useRulesStatuses } from '../../../../containers/detection_engine/rules'; +import { TestProviders } from '../../../../../common/mock'; +import { createUseUiSetting$Mock } from '../../../../../common/lib/kibana/kibana_react.mock'; +import { + DEFAULT_RULE_REFRESH_INTERVAL_ON, + DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + DEFAULT_RULE_REFRESH_IDLE_VALUE, + DEFAULT_RULES_TABLE_REFRESH_SETTING, +} from '../../../../../../common/constants'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -27,66 +35,33 @@ jest.mock('react-router-dom', () => { jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../../common/lib/kibana'); +jest.mock('../../../../containers/detection_engine/rules'); const useKibanaMock = useKibana as jest.Mocked; +const mockUseUiSetting$ = useUiSetting$ as jest.Mock; -jest.mock('./reducer', () => { - return { - allRulesReducer: jest.fn().mockReturnValue(() => ({ - exportRuleIds: [], - filterOptions: { - filter: 'some filter', - sortField: 'some sort field', - sortOrder: 'desc', - }, - loadingRuleIds: [], - loadingRulesAction: null, - pagination: { - page: 1, - perPage: 20, - total: 1, - }, - rules: [ - { - actions: [], - created_at: '2020-02-14T19:49:28.178Z', - created_by: 'elastic', - description: 'jibber jabber', - enabled: false, - false_positives: [], - filters: [], - from: 'now-660s', - id: 'rule-id-1', - immutable: true, - index: ['endgame-*'], - interval: '10m', - language: 'kuery', - max_signals: 100, - name: 'Credential Dumping - Detected - Elastic Endpoint', - output_index: '.siem-signals-default', - query: 'host.name:*', - references: [], - risk_score: 73, - rule_id: '571afc56-5ed9-465d-a2a9-045f099f6e7e', - severity: 'high', - tags: ['Elastic', 'Endpoint'], - threat: [], - throttle: null, - to: 'now', - type: 'query', - updated_at: '2020-02-14T19:49:28.320Z', - updated_by: 'elastic', - version: 1, - }, - ], - selectedRuleIds: [], - })), - }; -}); +describe('AllRules', () => { + const mockRefetchRulesData = jest.fn(); -jest.mock('../../../../containers/detection_engine/rules', () => { - return { - useRules: jest.fn().mockReturnValue([ + beforeEach(() => { + jest.useFakeTimers(); + + mockUseUiSetting$.mockImplementation((key, defaultValue) => { + const useUiSetting$Mock = createUseUiSetting$Mock(); + + return key === DEFAULT_RULES_TABLE_REFRESH_SETTING + ? [ + { + on: DEFAULT_RULE_REFRESH_INTERVAL_ON, + value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE, + }, + jest.fn(), + ] + : useUiSetting$Mock(key, defaultValue); + }); + + (useRules as jest.Mock).mockReturnValue([ false, { page: 1, @@ -126,8 +101,10 @@ jest.mock('../../../../containers/detection_engine/rules', () => { }, ], }, - ]), - useRulesStatuses: jest.fn().mockReturnValue({ + mockRefetchRulesData, + ]); + + (useRulesStatuses as jest.Mock).mockReturnValue({ loading: false, rulesStatuses: [ { @@ -150,21 +127,8 @@ jest.mock('../../../../containers/detection_engine/rules', () => { name: 'Test rule', }, ], - }), - }; -}); - -jest.mock('react-router-dom', () => { - const originalModule = jest.requireActual('react-router-dom'); - - return { - ...originalModule, - useHistory: jest.fn(), - }; -}); + }); -describe('AllRules', () => { - beforeEach(() => { useKibanaMock().services.application.capabilities = { navLinks: {}, management: {}, @@ -172,6 +136,12 @@ describe('AllRules', () => { actions: { show: true }, }; }); + + afterEach(() => { + jest.clearAllTimers(); + jest.clearAllMocks(); + }); + it('renders correctly', () => { const wrapper = shallow( { expect(wrapper.find('[title="All rules"]')).toHaveLength(1); }); + it('it pulls from uiSettings to determine default refresh values', async () => { + mount( + + + + ); + + await waitFor(() => { + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + expect(mockRefetchRulesData).toHaveBeenCalledTimes(1); + }); + }); + + // refresh functionality largely tested in cypress tests + it('it pulls from storage and does not set an auto refresh interval if storage indicates refresh is paused', async () => { + mockUseUiSetting$.mockImplementation(() => [ + { + on: false, + value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE, + }, + jest.fn(), + ]); + + const wrapper = mount( + + + + ); + + await waitFor(() => { + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + + wrapper.find('[data-test-subj="refreshSettings"] button').first().simulate('click'); + + wrapper.find('[data-test-subj="refreshSettingsSwitch"]').first().simulate('click'); + + jest.advanceTimersByTime(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + }); + }); + describe('rules tab', () => { - it('renders correctly', async () => { + it('renders all rules tab by default', async () => { const wrapper = mount( { /> ); - const monitoringTab = wrapper.find('[data-test-subj="allRulesTableTab-monitoring"] button'); - monitoringTab.simulate('click'); await waitFor(() => { + const monitoringTab = wrapper.find('[data-test-subj="allRulesTableTab-monitoring"] button'); + monitoringTab.simulate('click'); + wrapper.update(); expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeTruthy(); expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeFalsy(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx index 86b3daddd6c19b..663a4bb242c069 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx @@ -6,15 +6,18 @@ import { EuiBasicTable, - EuiContextMenuPanel, EuiLoadingContent, EuiSpacer, EuiTab, EuiTabs, + EuiProgress, + EuiOverlayMask, + EuiConfirmModal, } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; import uuid from 'uuid'; +import { debounce } from 'lodash/fp'; import { useRules, @@ -27,14 +30,7 @@ import { RulesSortingFields, } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../../common/components/header_section'; -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../../../../../common/components/utility_bar'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; import { useStateToaster } from '../../../../../common/components/toasters'; import { Loader } from '../../../../../common/components/loader'; import { Panel } from '../../../../../common/components/panel'; @@ -55,6 +51,9 @@ import { hasMlLicense } from '../../../../../../common/machine_learning/has_ml_l import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { isBoolean } from '../../../../../common/utils/privileges'; +import { AllRulesUtilityBar } from './utility_bar'; +import { LastUpdatedAt } from '../../../../../common/components/last_updated'; +import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/constants'; const INITIAL_SORT_FIELD = 'enabled'; const initialState: State = { @@ -73,6 +72,9 @@ const initialState: State = { }, rules: [], selectedRuleIds: [], + lastUpdated: 0, + showIdleModal: false, + isRefreshOn: true, }; interface AllRulesProps { @@ -129,6 +131,18 @@ export const AllRules = React.memo( }) => { const [initLoading, setInitLoading] = useState(true); const tableRef = useRef(); + const { + services: { + application: { + capabilities: { actions }, + }, + }, + } = useKibana(); + const [defaultAutoRefreshSetting] = useUiSetting$<{ + on: boolean; + value: number; + idleTimeout: number; + }>(DEFAULT_RULES_TABLE_REFRESH_SETTING); const [ { exportRuleIds, @@ -138,9 +152,16 @@ export const AllRules = React.memo( pagination, rules, selectedRuleIds, + lastUpdated, + showIdleModal, + isRefreshOn, }, dispatch, - ] = useReducer(allRulesReducer(tableRef), initialState); + ] = useReducer(allRulesReducer(tableRef), { + ...initialState, + lastUpdated: Date.now(), + isRefreshOn: defaultAutoRefreshSetting.on, + }); const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const history = useHistory(); const [, dispatchToaster] = useStateToaster(); @@ -159,6 +180,26 @@ export const AllRules = React.memo( }); }, []); + const setShowIdleModal = useCallback((show: boolean) => { + dispatch({ + type: 'setShowIdleModal', + show, + }); + }, []); + + const setLastRefreshDate = useCallback(() => { + dispatch({ + type: 'setLastRefreshDate', + }); + }, []); + + const setAutoRefreshOn = useCallback((on: boolean) => { + dispatch({ + type: 'setAutoRefreshOn', + on, + }); + }, []); + const [isLoadingRules, , reFetchRulesData] = useRules({ pagination, filterOptions, @@ -181,34 +222,25 @@ export const AllRules = React.memo( rulesNotInstalled, rulesNotUpdated ); - const { - services: { - application: { - capabilities: { actions }, - }, - }, - } = useKibana(); const hasActionsPrivileges = useMemo(() => (isBoolean(actions.show) ? actions.show : true), [ actions, ]); const getBatchItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), + (closePopover: () => void): JSX.Element[] => { + return getBatchItems({ + closePopover, + dispatch, + dispatchToaster, + hasMlPermissions, + hasActionsPrivileges, + loadingRuleIds, + selectedRuleIds, + reFetchRules: reFetchRulesData, + rules, + }); + }, [ dispatch, dispatchToaster, @@ -328,6 +360,94 @@ export const AllRules = React.memo( return false; }, [loadingRuleIds, loadingRulesAction]); + const handleRefreshData = useCallback((): void => { + if (reFetchRulesData != null && !isLoadingAnActionOnRule) { + reFetchRulesData(true); + setLastRefreshDate(); + } + }, [reFetchRulesData, isLoadingAnActionOnRule, setLastRefreshDate]); + + const handleResetIdleTimer = useCallback((): void => { + if (isRefreshOn) { + setShowIdleModal(true); + setAutoRefreshOn(false); + } + }, [setShowIdleModal, setAutoRefreshOn, isRefreshOn]); + + const debounceResetIdleTimer = useMemo(() => { + return debounce(defaultAutoRefreshSetting.idleTimeout, handleResetIdleTimer); + }, [handleResetIdleTimer, defaultAutoRefreshSetting.idleTimeout]); + + useEffect(() => { + const interval = setInterval(() => { + if (isRefreshOn) { + handleRefreshData(); + } + }, defaultAutoRefreshSetting.value); + + return () => { + clearInterval(interval); + }; + }, [isRefreshOn, handleRefreshData, defaultAutoRefreshSetting.value]); + + const handleIdleModalContinue = useCallback((): void => { + setShowIdleModal(false); + handleRefreshData(); + setAutoRefreshOn(true); + }, [setShowIdleModal, setAutoRefreshOn, handleRefreshData]); + + const handleAutoRefreshSwitch = useCallback( + (refreshOn: boolean) => { + if (refreshOn) { + handleRefreshData(); + } + setAutoRefreshOn(refreshOn); + }, + [setAutoRefreshOn, handleRefreshData] + ); + + useEffect(() => { + debounceResetIdleTimer(); + + window.addEventListener('mousemove', debounceResetIdleTimer, { passive: true }); + window.addEventListener('keydown', debounceResetIdleTimer); + + return () => { + window.removeEventListener('mousemove', debounceResetIdleTimer); + window.removeEventListener('keydown', debounceResetIdleTimer); + }; + }, [handleResetIdleTimer, debounceResetIdleTimer]); + + const shouldShowRulesTable = useMemo( + (): boolean => showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading, + [initLoading, rulesCustomInstalled, rulesInstalled] + ); + + const shouldShowPrepackagedRulesPrompt = useMemo( + (): boolean => + rulesCustomInstalled != null && + rulesCustomInstalled === 0 && + prePackagedRuleStatus === 'ruleNotInstalled' && + !initLoading, + [initLoading, prePackagedRuleStatus, rulesCustomInstalled] + ); + + const handleGenericDownloaderSuccess = useCallback( + (exportCount) => { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), + color: 'success', + iconType: 'check', + }, + }); + }, + [dispatchToaster] + ); + const tabs = useMemo( () => ( @@ -353,27 +473,37 @@ export const AllRules = React.memo( { - dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); - dispatchToaster({ - type: 'addToaster', - toast: { - id: uuid.v4(), - title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), - color: 'success', - iconType: 'check', - }, - }); - }} + onExportSuccess={handleGenericDownloaderSuccess} exportSelectedData={exportRules} /> {tabs} - + <> - + {(isLoadingRules || isLoadingRulesStatuses) && ( + + )} + + } + > ( /> - {(loading || isLoadingRules || isLoadingAnActionOnRule || isLoadingRulesStatuses) && - !initLoading && ( - - )} - {rulesCustomInstalled != null && - rulesCustomInstalled === 0 && - prePackagedRuleStatus === 'ruleNotInstalled' && - !initLoading && ( - - )} + {isLoadingAnActionOnRule && !initLoading && ( + + )} + {shouldShowPrepackagedRulesPrompt && ( + + )} {initLoading && ( )} - {showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading && ( + {showIdleModal && ( + + +

{i18n.REFRESH_PROMPT_BODY}

+
+
+ )} + {shouldShowRulesTable && ( <> - - - - - {i18n.SHOWING_RULES(pagination.total ?? 0)} - - - - - {i18n.SELECTED_RULES(selectedRuleIds.length)} - {!hasNoPermissions && ( - - {i18n.BATCH_ACTIONS} - - )} - reFetchRulesData(true)} - > - {i18n.REFRESH} - - - - + { + let reducer: (state: State, action: Action) => State; + + beforeEach(() => { + jest.useFakeTimers(); + jest + .spyOn(global.Date, 'now') + .mockImplementationOnce(() => new Date('2020-10-31T11:01:58.135Z').valueOf()); + reducer = allRulesReducer({ current: undefined }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('#exportRuleIds', () => { + test('should update state with rules to be exported', () => { + const { loadingRuleIds, loadingRulesAction, exportRuleIds } = reducer(initialState, { + type: 'exportRuleIds', + ids: ['123', '456'], + }); + + expect(loadingRuleIds).toEqual(['123', '456']); + expect(exportRuleIds).toEqual(['123', '456']); + expect(loadingRulesAction).toEqual('export'); + }); + }); + + describe('#loadingRuleIds', () => { + test('should update state with rule ids with a pending action', () => { + const { loadingRuleIds, loadingRulesAction } = reducer(initialState, { + type: 'loadingRuleIds', + ids: ['123', '456'], + actionType: 'enable', + }); + + expect(loadingRuleIds).toEqual(['123', '456']); + expect(loadingRulesAction).toEqual('enable'); + }); + + test('should update loadingIds to empty array if action is null', () => { + const { loadingRuleIds, loadingRulesAction } = reducer(initialState, { + type: 'loadingRuleIds', + ids: ['123', '456'], + actionType: null, + }); + + expect(loadingRuleIds).toEqual([]); + expect(loadingRulesAction).toBeNull(); + }); + + test('should append rule ids to any existing loading ids', () => { + const { loadingRuleIds, loadingRulesAction } = reducer( + { ...initialState, loadingRuleIds: ['abc'] }, + { + type: 'loadingRuleIds', + ids: ['123', '456'], + actionType: 'duplicate', + } + ); + + expect(loadingRuleIds).toEqual(['abc', '123', '456']); + expect(loadingRulesAction).toEqual('duplicate'); + }); + }); + + describe('#selectedRuleIds', () => { + test('should update state with selected rule ids', () => { + const { selectedRuleIds } = reducer(initialState, { + type: 'selectedRuleIds', + ids: ['123', '456'], + }); + + expect(selectedRuleIds).toEqual(['123', '456']); + }); + }); + + describe('#setRules', () => { + test('should update rules and reset loading/selected rule ids', () => { + const { selectedRuleIds, loadingRuleIds, loadingRulesAction, pagination, rules } = reducer( + initialState, + { + type: 'setRules', + rules: [mockRule('someRuleId')], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + } + ); + + expect(rules).toEqual([mockRule('someRuleId')]); + expect(selectedRuleIds).toEqual([]); + expect(loadingRuleIds).toEqual([]); + expect(loadingRulesAction).toBeNull(); + expect(pagination).toEqual({ + page: 1, + perPage: 20, + total: 0, + }); + }); + }); + + describe('#updateRules', () => { + test('should return existing and new rules', () => { + const existingRule = { ...mockRule('123'), rule_id: 'rule-123' }; + const { rules, loadingRulesAction } = reducer( + { ...initialState, rules: [existingRule] }, + { + type: 'updateRules', + rules: [mockRule('someRuleId')], + } + ); + + expect(rules).toEqual([existingRule, mockRule('someRuleId')]); + expect(loadingRulesAction).toBeNull(); + }); + + test('should return updated rule', () => { + const updatedRule = { ...mockRule('someRuleId'), description: 'updated rule' }; + const { rules, loadingRulesAction } = reducer( + { ...initialState, rules: [mockRule('someRuleId')] }, + { + type: 'updateRules', + rules: [updatedRule], + } + ); + + expect(rules).toEqual([updatedRule]); + expect(loadingRulesAction).toBeNull(); + }); + + test('should return updated existing loading rule ids', () => { + const existingRule = { ...mockRule('someRuleId'), id: '123', rule_id: 'rule-123' }; + const { loadingRuleIds, loadingRulesAction } = reducer( + { + ...initialState, + rules: [existingRule], + loadingRuleIds: ['123'], + loadingRulesAction: 'enable', + }, + { + type: 'updateRules', + rules: [mockRule('someRuleId')], + } + ); + + expect(loadingRuleIds).toEqual(['123']); + expect(loadingRulesAction).toEqual('enable'); + }); + }); + + describe('#updateFilterOptions', () => { + test('should return existing and new rules', () => { + const paginationMock: PaginationOptions = { + page: 1, + perPage: 20, + total: 0, + }; + const filterMock: FilterOptions = { + filter: 'host.name:*', + sortField: 'enabled', + sortOrder: 'desc', + }; + const { filterOptions, pagination } = reducer(initialState, { + type: 'updateFilterOptions', + filterOptions: filterMock, + pagination: paginationMock, + }); + + expect(filterOptions).toEqual(filterMock); + expect(pagination).toEqual(paginationMock); + }); + }); + + describe('#failure', () => { + test('should reset rules value to empty array', () => { + const { rules } = reducer(initialState, { + type: 'failure', + }); + + expect(rules).toEqual([]); + }); + }); + + describe('#setLastRefreshDate', () => { + test('should update last refresh date with current date', () => { + const { lastUpdated } = reducer(initialState, { + type: 'setLastRefreshDate', + }); + + expect(lastUpdated).toEqual(1604142118135); + }); + }); + + describe('#setShowIdleModal', () => { + test('should hide idle modal and restart refresh if "show" is false', () => { + const { showIdleModal, isRefreshOn } = reducer(initialState, { + type: 'setShowIdleModal', + show: false, + }); + + expect(showIdleModal).toBeFalsy(); + expect(isRefreshOn).toBeTruthy(); + }); + + test('should show idle modal and pause refresh if "show" is true', () => { + const { showIdleModal, isRefreshOn } = reducer(initialState, { + type: 'setShowIdleModal', + show: true, + }); + + expect(showIdleModal).toBeTruthy(); + expect(isRefreshOn).toBeFalsy(); + }); + }); + + describe('#setAutoRefreshOn', () => { + test('should pause auto refresh if "paused" is true', () => { + const { isRefreshOn } = reducer(initialState, { + type: 'setAutoRefreshOn', + on: true, + }); + + expect(isRefreshOn).toBeTruthy(); + }); + + test('should resume auto refresh if "paused" is false', () => { + const { isRefreshOn } = reducer(initialState, { + type: 'setAutoRefreshOn', + on: false, + }); + + expect(isRefreshOn).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts index ff9b41bed06f5e..d603e5791f5ce1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts @@ -20,6 +20,9 @@ export interface State { pagination: PaginationOptions; rules: Rule[]; selectedRuleIds: string[]; + lastUpdated: number; + showIdleModal: boolean; + isRefreshOn: boolean; } export type Action = @@ -33,7 +36,10 @@ export type Action = filterOptions: Partial; pagination: Partial; } - | { type: 'failure' }; + | { type: 'failure' } + | { type: 'setLastRefreshDate' } + | { type: 'setShowIdleModal'; show: boolean } + | { type: 'setAutoRefreshOn'; on: boolean }; export const allRulesReducer = ( tableRef: React.MutableRefObject | undefined> @@ -85,27 +91,24 @@ export const allRulesReducer = ( }; } case 'updateRules': { - if (state.rules != null) { - const ruleIds = state.rules.map((r) => r.id); - const updatedRules = action.rules.reduce((rules, updatedRule) => { - let newRules = rules; - if (ruleIds.includes(updatedRule.id)) { - newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); - } else { - newRules = [...newRules, updatedRule]; - } - return newRules; - }, state.rules); - const updatedRuleIds = action.rules.map((r) => r.id); - const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); - return { - ...state, - rules: updatedRules, - loadingRuleIds: newLoadingRuleIds, - loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, - }; - } - return state; + const ruleIds = state.rules.map((r) => r.id); + const updatedRules = action.rules.reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.id)) { + newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); + const updatedRuleIds = action.rules.map((r) => r.id); + const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); + return { + ...state, + rules: updatedRules, + loadingRuleIds: newLoadingRuleIds, + loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, + }; } case 'updateFilterOptions': { return { @@ -126,6 +129,25 @@ export const allRulesReducer = ( rules: [], }; } + case 'setLastRefreshDate': { + return { + ...state, + lastUpdated: Date.now(), + }; + } + case 'setShowIdleModal': { + return { + ...state, + showIdleModal: action.show, + isRefreshOn: !action.show, + }; + } + case 'setAutoRefreshOn': { + return { + ...state, + isRefreshOn: action.on, + }; + } default: return state; } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx index 92f69d79110d2b..a8205c24dca65f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -5,16 +5,47 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import { act } from '@testing-library/react'; import { RulesTableFilters } from './rules_table_filters'; describe('RulesTableFilters', () => { - it('renders correctly', () => { - const wrapper = shallow( - - ); + it('renders no numbers next to rule type button filter if none exist', async () => { + await act(async () => { + const wrapper = mount( + + ); - expect(wrapper.find('[data-test-subj="show-elastic-rules-filter-button"]')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="showElasticRulesFilterButton"]').at(0).text()).toEqual( + 'Elastic rules' + ); + expect(wrapper.find('[data-test-subj="showCustomRulesFilterButton"]').at(0).text()).toEqual( + 'Custom rules' + ); + }); + }); + + it('renders number of custom and prepackaged rules', async () => { + await act(async () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="showElasticRulesFilterButton"]').at(0).text()).toEqual( + 'Elastic rules (9)' + ); + expect(wrapper.find('[data-test-subj="showCustomRulesFilterButton"]').at(0).text()).toEqual( + 'Custom rules (10)' + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx index 0f201fcbaa441a..0b83a8437cc1ac 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -14,6 +14,7 @@ import { EuiFlexItem, } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; + import * as i18n from '../../translations'; import { FilterOptions } from '../../../../../containers/detection_engine/rules'; @@ -76,7 +77,7 @@ const RulesTableFiltersComponent = ({ return ( - + @@ -102,7 +104,7 @@ const RulesTableFiltersComponent = ({ {i18n.ELASTIC_RULES} @@ -111,7 +113,7 @@ const RulesTableFiltersComponent = ({ <> {i18n.CUSTOM_RULES} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx new file mode 100644 index 00000000000000..3d49295bde50a0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { mount } from 'enzyme'; +import { ThemeProvider } from 'styled-components'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { waitFor } from '@testing-library/react'; + +import { AllRulesUtilityBar } from './utility_bar'; + +describe('AllRules', () => { + it('renders AllRulesUtilityBar total rules and selected rules', () => { + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + expect(wrapper.find('[data-test-subj="showingRules"]').at(0).text()).toEqual('Showing 4 rules'); + expect(wrapper.find('[data-test-subj="selectedRules"]').at(0).text()).toEqual( + 'Selected 1 rule' + ); + }); + + it('renders utility actions if user has permissions', () => { + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + expect(wrapper.find('[data-test-subj="bulkActions"]').exists()).toBeTruthy(); + }); + + it('renders no utility actions if user has no permissions', () => { + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + expect(wrapper.find('[data-test-subj="bulkActions"]').exists()).toBeFalsy(); + }); + + it('invokes refresh on refresh action click', () => { + const mockRefresh = jest.fn(); + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + wrapper.find('[data-test-subj="refreshRulesAction"] button').at(0).simulate('click'); + + expect(mockRefresh).toHaveBeenCalled(); + }); + + it('invokes onRefreshSwitch when auto refresh switch is clicked', async () => { + const mockSwitch = jest.fn(); + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + await waitFor(() => { + wrapper.find('[data-test-subj="refreshSettings"] button').first().simulate('click'); + wrapper.find('[data-test-subj="refreshSettingsSwitch"] button').first().simulate('click'); + expect(mockSwitch).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx new file mode 100644 index 00000000000000..3553dcc9b7c143 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { EuiContextMenuPanel, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import React, { useCallback } from 'react'; + +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../../../common/components/utility_bar'; +import * as i18n from '../translations'; + +interface AllRulesUtilityBarProps { + userHasNoPermissions: boolean; + numberSelectedRules: number; + paginationTotal: number; + isAutoRefreshOn: boolean; + onRefresh: (refreshRule: boolean) => void; + onGetBatchItemsPopoverContent: (closePopover: () => void) => JSX.Element[]; + onRefreshSwitch: (checked: boolean) => void; +} + +export const AllRulesUtilityBar = React.memo( + ({ + userHasNoPermissions, + onRefresh, + paginationTotal, + numberSelectedRules, + onGetBatchItemsPopoverContent, + isAutoRefreshOn, + onRefreshSwitch, + }) => { + const handleGetBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [onGetBatchItemsPopoverContent] + ); + + const handleAutoRefreshSwitch = useCallback( + (closePopover: () => void) => (e: EuiSwitchEvent) => { + onRefreshSwitch(e.target.checked); + closePopover(); + }, + [onRefreshSwitch] + ); + + const handleGetRefreshSettingsPopoverContent = useCallback( + (closePopover: () => void) => ( + , + ]} + /> + ), + [isAutoRefreshOn, handleAutoRefreshSwitch] + ); + + return ( + + + + + {i18n.SHOWING_RULES(paginationTotal)} + + + + + + {i18n.SELECTED_RULES(numberSelectedRules)} + + {!userHasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} + + {i18n.REFRESH} + + + {i18n.REFRESH_RULE_POPOVER_LABEL} + + + + + ); + } +); + +AllRulesUtilityBar.displayName = 'AllRulesUtilityBar'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index d20b97a98fbf5b..38fb457185b67b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -554,3 +554,38 @@ export const IMPORT_FAILED_DETAILED = (ruleId: string, statusCode: number, messa defaultMessage: 'Rule ID: {ruleId}\n Status Code: {statusCode}\n Message: {message}', } ); + +export const REFRESH_PROMPT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.refreshPromptTitle', + { + defaultMessage: 'Are you still there?', + } +); + +export const REFRESH_PROMPT_CONFIRM = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.refreshPromptConfirm', + { + defaultMessage: 'Continue', + } +); + +export const REFRESH_PROMPT_BODY = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.refreshPromptBody', + { + defaultMessage: 'Rule auto-refresh has been paused. Click "Continue" to resume.', + } +); + +export const REFRESH_RULE_POPOVER_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.refreshRulePopoverDescription', + { + defaultMessage: 'Automatically refresh table', + } +); + +export const REFRESH_RULE_POPOVER_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.refreshRulePopoverLabel', + { + defaultMessage: 'Refresh settings', + } +); 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 4119127d5a108d..f56d7d90cf2df8 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 @@ -25,10 +25,10 @@ import styled from 'styled-components'; import { LoadingPanel } from '../../loading'; import { OnChangeItemsPerPage, OnChangePage } from '../events'; -import { LastUpdatedAt } from './last_updated'; import * as i18n from './translations'; import { useEventDetailsWidthContext } from '../../../../common/components/events_viewer/event_details_width_context'; import { useManageTimeline } from '../../manage_timeline'; +import { LastUpdatedAt } from '../../../../common/components/last_updated'; export const isCompactFooter = (width: number): boolean => width < 600; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx deleted file mode 100644 index 06ece50690c09c..00000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx +++ /dev/null @@ -1,68 +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 { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; -import React, { useEffect, useState } from 'react'; - -import * as i18n from './translations'; - -interface LastUpdatedAtProps { - compact?: boolean; - updatedAt: number; -} - -export const Updated = React.memo<{ date: number; prefix: string; updatedAt: number }>( - ({ date, prefix, updatedAt }) => ( - <> - {prefix} - { - - } - - ) -); - -Updated.displayName = 'Updated'; - -const prefix = ` ${i18n.UPDATED} `; - -export const LastUpdatedAt = React.memo(({ compact = false, updatedAt }) => { - const [date, setDate] = useState(Date.now()); - - function tick() { - setDate(Date.now()); - } - - useEffect(() => { - const timerID = setInterval(() => tick(), 10000); - return () => { - clearInterval(timerID); - }; - }, []); - - return ( - - - - } - > - - - {!compact ? : null} - - - ); -}); - -LastUpdatedAt.displayName = 'LastUpdatedAt'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts index f581d0757bc3cf..016406d6bd061b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts @@ -36,10 +36,6 @@ export const TOTAL_COUNT_OF_EVENTS = i18n.translate( } ); -export const UPDATED = i18n.translate('xpack.securitySolution.footer.updated', { - defaultMessage: 'Updated', -}); - export const AUTO_REFRESH_ACTIVE = i18n.translate( 'xpack.securitySolution.footer.autoRefreshActiveDescription', { diff --git a/x-pack/plugins/security_solution/server/ui_settings.ts b/x-pack/plugins/security_solution/server/ui_settings.ts index 4b5261edcdfd0e..6b10a9909e19cf 100644 --- a/x-pack/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/plugins/security_solution/server/ui_settings.ts @@ -23,6 +23,10 @@ import { NEWS_FEED_URL_SETTING_DEFAULT, IP_REPUTATION_LINKS_SETTING, IP_REPUTATION_LINKS_SETTING_DEFAULT, + DEFAULT_RULES_TABLE_REFRESH_SETTING, + DEFAULT_RULE_REFRESH_INTERVAL_ON, + DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + DEFAULT_RULE_REFRESH_IDLE_VALUE, } from '../common/constants'; export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { @@ -112,6 +116,31 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { requiresPageReload: true, schema: schema.boolean(), }, + [DEFAULT_RULES_TABLE_REFRESH_SETTING]: { + name: i18n.translate('xpack.securitySolution.uiSettings.rulesTableRefresh', { + defaultMessage: 'Rules auto refresh', + }), + description: i18n.translate( + 'xpack.securitySolution.uiSettings.rulesTableRefreshDescription', + { + defaultMessage: + '

Enables auto refresh on the all rules and monitoring tables, in milliseconds

', + } + ), + type: 'json', + value: `{ + "on": ${DEFAULT_RULE_REFRESH_INTERVAL_ON}, + "value": ${DEFAULT_RULE_REFRESH_INTERVAL_VALUE}, + "idleTimeout": ${DEFAULT_RULE_REFRESH_IDLE_VALUE} +}`, + category: ['securitySolution'], + requiresPageReload: true, + schema: schema.object({ + idleTimeout: schema.number({ min: 300000 }), + value: schema.number({ min: 60000 }), + on: schema.boolean(), + }), + }, [NEWS_FEED_URL_SETTING]: { name: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrl', { defaultMessage: 'News feed URL', diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 85f0290cb5b1d4..e7784846598e47 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17825,7 +17825,6 @@ "xpack.securitySolution.footer.of": "/", "xpack.securitySolution.footer.rows": "行", "xpack.securitySolution.footer.totalCountOfEvents": "イベント", - "xpack.securitySolution.footer.updated": "更新しました", "xpack.securitySolution.formatted.duration.aFewMillisecondsTooltip": "数ミリ秒", "xpack.securitySolution.formatted.duration.aFewNanosecondsTooltip": "数ナノ秒", "xpack.securitySolution.formatted.duration.aMillisecondTooltip": "1 ミリ秒", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 59399d9278aa0d..f3cd662bacba71 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17844,7 +17844,6 @@ "xpack.securitySolution.footer.of": "/", "xpack.securitySolution.footer.rows": "行", "xpack.securitySolution.footer.totalCountOfEvents": "事件", - "xpack.securitySolution.footer.updated": "已更新", "xpack.securitySolution.formatted.duration.aFewMillisecondsTooltip": "几毫秒", "xpack.securitySolution.formatted.duration.aFewNanosecondsTooltip": "几纳秒", "xpack.securitySolution.formatted.duration.aMillisecondTooltip": "一毫秒", From e61c76d2d159a9815e115dc1e566bdd0e62d1be8 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Fri, 6 Nov 2020 15:28:27 -0600 Subject: [PATCH 42/81] Add flot_chart dependency from shared_deps to Shareable Runtime (#81649) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/canvas/shareable_runtime/api/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/canvas/shareable_runtime/api/index.ts b/x-pack/plugins/canvas/shareable_runtime/api/index.ts index 0780ab46cd873b..dc7445eb7bc5af 100644 --- a/x-pack/plugins/canvas/shareable_runtime/api/index.ts +++ b/x-pack/plugins/canvas/shareable_runtime/api/index.ts @@ -7,5 +7,7 @@ import 'core-js/stable'; import 'regenerator-runtime/runtime'; import 'whatwg-fetch'; +import 'jquery'; +import '@kbn/ui-shared-deps/flot_charts'; export * from './shareable'; From ae20a3a2a94a131dc6aafa84a22b4508e9ba1b9b Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Fri, 6 Nov 2020 16:51:14 -0500 Subject: [PATCH 43/81] [ML] Add unsigned_long support to data frame analytics and anomaly detection (#82636) * add support for unsigned_long field in dfa * add support for unsigned_long for anomaly detection --- .../ml/public/application/data_frame_analytics/common/fields.ts | 1 + .../ml/server/models/job_service/new_job_caps/field_service.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index 785f3ac9cd4dcf..d46adff3de2a32 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -33,6 +33,7 @@ export const MAX_COLUMNS = 10; export const DEFAULT_REGRESSION_COLUMNS = 8; export const BASIC_NUMERICAL_TYPES = new Set([ + ES_FIELD_TYPES.UNSIGNED_LONG, ES_FIELD_TYPES.LONG, ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.SHORT, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index c3b1de64c3eb5e..fec60f221b4fca 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -27,6 +27,7 @@ const supportedTypes: string[] = [ ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.FLOAT, ES_FIELD_TYPES.LONG, + ES_FIELD_TYPES.UNSIGNED_LONG, ES_FIELD_TYPES.BYTE, ES_FIELD_TYPES.HALF_FLOAT, ES_FIELD_TYPES.SCALED_FLOAT, @@ -245,6 +246,7 @@ function getNumericalFields(fields: Field[]): Field[] { return fields.filter( (f) => f.type === ES_FIELD_TYPES.LONG || + f.type === ES_FIELD_TYPES.UNSIGNED_LONG || f.type === ES_FIELD_TYPES.INTEGER || f.type === ES_FIELD_TYPES.SHORT || f.type === ES_FIELD_TYPES.BYTE || From e8ec392a94f737b306b5fbfdad335b756d82b8b2 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Fri, 6 Nov 2020 17:21:29 -0500 Subject: [PATCH 44/81] Move single use function in line (#82885) ## Summary * Function rename to follow existing get/fetch convention ```diff - export async function loadRegistryPackage( + export async function getRegistryPackage( ``` * Moved `unpackRegistryPackageToCache` into `getRegistryPackage` * In my opinion, those three/four statements are more clear than the previous name * It isn't used anywhere else --- .../server/services/epm/packages/get.ts | 2 +- .../server/services/epm/packages/install.ts | 2 +- .../server/services/epm/registry/index.ts | 26 ++++++------------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index 2021b353f1a279..893df1733c58bf 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -116,7 +116,7 @@ export async function getPackageInfo(options: { ] = await Promise.all([ getInstallationObject({ savedObjectsClient, pkgName }), Registry.fetchFindLatestPackage(pkgName), - Registry.loadRegistryPackage(pkgName, pkgVersion), + Registry.getRegistryPackage(pkgName, pkgVersion), ]); // add properties that aren't (or aren't yet) on Registry response diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 0496a6e9aeef1e..e7d8c8d4695d4e 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -248,7 +248,7 @@ export async function installPackageFromRegistry({ throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); } - const { paths, registryPackageInfo } = await Registry.loadRegistryPackage(pkgName, pkgVersion); + const { paths, registryPackageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); const removable = !isRequiredPackage(pkgName); const { internal = false } = registryPackageInfo; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 013315eb6da235..52a1894570b2a2 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -20,7 +20,6 @@ import { } from '../../../types'; import { unpackArchiveToCache } from '../archive'; import { cacheGet, getArchiveFilelist, setArchiveFilelist } from '../archive'; -import { ArchiveEntry } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; import { getRegistryUrl } from './registry_url'; @@ -126,27 +125,18 @@ export async function fetchCategories(params?: CategoriesParams): Promise true -): Promise { - const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion); - const contentType = mime.lookup(archivePath); - if (!contentType) { - throw new Error(`Unknown compression format for '${archivePath}'. Please use .zip or .gz`); - } - const paths: string[] = await unpackArchiveToCache(archiveBuffer, contentType); - return paths; -} - -export async function loadRegistryPackage( +export async function getRegistryPackage( pkgName: string, pkgVersion: string ): Promise<{ paths: string[]; registryPackageInfo: RegistryPackage }> { let paths = getArchiveFilelist(pkgName, pkgVersion); if (!paths || paths.length === 0) { - paths = await unpackRegistryPackageToCache(pkgName, pkgVersion); + const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion); + const contentType = mime.lookup(archivePath); + if (!contentType) { + throw new Error(`Unknown compression format for '${archivePath}'. Please use .zip or .gz`); + } + paths = await unpackArchiveToCache(archiveBuffer, contentType); setArchiveFilelist(pkgName, pkgVersion, paths); } @@ -194,7 +184,7 @@ export async function ensureCachedArchiveInfo( const paths = getArchiveFilelist(name, version); if (!paths || paths.length === 0) { if (installSource === 'registry') { - await loadRegistryPackage(name, version); + await getRegistryPackage(name, version); } else { throw new PackageCacheError( `Package ${name}-${version} not cached. If it was uploaded, try uninstalling and reinstalling manually.` From b08677b90483315210aa28412dbf8e2cc56f4433 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Fri, 6 Nov 2020 15:26:49 -0800 Subject: [PATCH 45/81] [APM] Adds new configuration 'xpack.apm.maxServiceEnvironments' (#82090) * Closes #77695. Adds new configuration 'xpack.apm.ui.maxServiceEnvironments' to set the max number of service environments visible in APM UI. * renamed config 'xpack.apm.ui.maxServiceEnvironments' -> 'xpack.apm.maxServiceEnvironments' * Renames 'xpack.apm.ui.maxServiceEnvironments' -> 'xpack.apm.maxServiceEnvironments' in the docs. * removed incorrect size param on the composite terms sub-agg Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/settings/apm-settings.asciidoc | 3 +++ x-pack/plugins/apm/server/index.ts | 2 ++ .../apm/server/lib/alerts/register_error_count_alert_type.ts | 2 ++ .../lib/alerts/register_transaction_duration_alert_type.ts | 2 ++ .../lib/alerts/register_transaction_error_rate_alert_type.ts | 2 ++ .../apm/server/lib/environments/get_all_environments.ts | 5 +++-- .../server/lib/services/__snapshots__/queries.test.ts.snap | 1 + .../lib/services/get_services/get_services_items_stats.ts | 4 +++- .../agent_configuration/__snapshots__/queries.test.ts.snap | 2 +- .../get_existing_environments_for_service.ts | 5 +++-- .../server/lib/ui_filters/__snapshots__/queries.test.ts.snap | 2 ++ x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts | 5 ++++- x-pack/plugins/apm/server/utils/test_helpers.tsx | 3 +++ 13 files changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index b32c340df4adfd..79fa9a642428af 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -43,6 +43,9 @@ Changing these settings may disable features of the APM App. | `xpack.apm.enabled` | Set to `false` to disable the APM app. Defaults to `true`. +| `xpack.apm.maxServiceEnvironments` + | Maximum number of unique service environments recognized by the UI. Defaults to `100`. + | `xpack.apm.serviceMapFingerprintBucketSize` | Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`. diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 090110b0454c03..29a0d1fdf42494 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -43,6 +43,7 @@ export const config = { ), telemetryCollectionEnabled: schema.boolean({ defaultValue: true }), metricsInterval: schema.number({ defaultValue: 30 }), + maxServiceEnvironments: schema.number({ defaultValue: 100 }), }), }; @@ -74,6 +75,7 @@ export function mergeConfigs( 'xpack.apm.serviceMapMaxTracesPerRequest': apmConfig.serviceMapMaxTracesPerRequest, 'xpack.apm.ui.enabled': apmConfig.ui.enabled, + 'xpack.apm.maxServiceEnvironments': apmConfig.maxServiceEnvironments, 'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems, 'xpack.apm.ui.transactionGroupBucketSize': apmConfig.ui.transactionGroupBucketSize, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts index 7b63f2c354916c..ecda5b0e8504bf 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts @@ -66,6 +66,7 @@ export function registerErrorCountAlertType({ config, savedObjectsClient: services.savedObjectsClient, }); + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const searchParams = { index: indices['apm_oss.errorIndices'], @@ -100,6 +101,7 @@ export function registerErrorCountAlertType({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 1d8b664751ba29..d9e69c8f3b7d7c 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -75,6 +75,7 @@ export function registerTransactionDurationAlertType({ config, savedObjectsClient: services.savedObjectsClient, }); + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const searchParams = { index: indices['apm_oss.transactionIndices'], @@ -112,6 +113,7 @@ export function registerTransactionDurationAlertType({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts index 969f4ceaca93a9..06b296db5a4853 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts @@ -71,6 +71,7 @@ export function registerTransactionErrorRateAlertType({ config, savedObjectsClient: services.savedObjectsClient, }); + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const searchParams = { index: indices['apm_oss.transactionIndices'], @@ -120,6 +121,7 @@ export function registerTransactionErrorRateAlertType({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 95ff357937d471..39b4f7a7fe81b5 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -24,7 +24,8 @@ export async function getAllEnvironments({ searchAggregatedTransactions: boolean; includeMissing?: boolean; }) { - const { apmEventClient } = setup; + const { apmEventClient, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; // omit filter for service.name if "All" option is selected const serviceNameFilter = serviceName @@ -55,7 +56,7 @@ export async function getAllEnvironments({ environments: { terms: { field: SERVICE_ENVIRONMENT, - size: 100, + size: maxServiceEnvironments, ...(!serviceName ? { min_doc_count: 0 } : {}), missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined, }, diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 3a38f80c87b35e..a6818f96c728ee 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -366,6 +366,7 @@ Array [ "environments": Object { "terms": Object { "field": "service.environment", + "size": 100, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index 7d190c5b664501..fac80cf22c310e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -337,7 +337,8 @@ export const getEnvironments = async ({ setup, projection, }: AggregationParams) => { - const { apmEventClient } = setup; + const { apmEventClient, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const response = await apmEventClient.search( mergeProjection(projection, { body: { @@ -352,6 +353,7 @@ export const getEnvironments = async ({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap index 8db97a4929eb05..18ef3f44331d98 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap @@ -127,7 +127,7 @@ Object { "terms": Object { "field": "service.environment", "missing": "ALL_OPTION_VALUE", - "size": 50, + "size": 100, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index 8327ac59a95b2f..5e19f8f211cf70 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -18,7 +18,8 @@ export async function getExistingEnvironmentsForService({ serviceName: string | undefined; setup: Setup; }) { - const { internalClient, indices } = setup; + const { internalClient, indices, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const bool = serviceName ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] } @@ -34,7 +35,7 @@ export async function getExistingEnvironmentsForService({ terms: { field: SERVICE_ENVIRONMENT, missing: ALL_OPTION_VALUE, - size: 50, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap index d94b766aee6a89..3baaefe203ce75 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap @@ -15,6 +15,7 @@ Object { "terms": Object { "field": "service.environment", "missing": "ENVIRONMENT_NOT_DEFINED", + "size": 100, }, }, }, @@ -58,6 +59,7 @@ Object { "terms": Object { "field": "service.environment", "missing": "ENVIRONMENT_NOT_DEFINED", + "size": 100, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts index e72cc7e2483ad4..b9f25e20f9f730 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -24,7 +24,7 @@ export async function getEnvironments({ serviceName?: string; searchAggregatedTransactions: boolean; }) { - const { start, end, apmEventClient } = setup; + const { start, end, apmEventClient, config } = setup; const filter: ESFilter[] = [{ range: rangeFilter(start, end) }]; @@ -34,6 +34,8 @@ export async function getEnvironments({ }); } + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; + const params = { apm: { events: [ @@ -56,6 +58,7 @@ export async function getEnvironments({ terms: { field: SERVICE_ENVIRONMENT, missing: ENVIRONMENT_NOT_DEFINED.value, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index 18b990b35b5a58..21b59dc516d060 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -76,6 +76,9 @@ export async function inspectSearchParams( case 'xpack.apm.metricsInterval': return 30; + + case 'xpack.apm.maxServiceEnvironments': + return 100; } }, } From 802c6dccb41b95a77397cc8c41ebcc8a09b1bdcb Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 6 Nov 2020 16:20:39 -0800 Subject: [PATCH 46/81] Implemented Alerting health status pusher by using task manager and status pooler for Kibana status plugins 'kibanahost/api/status' (#79056) * Implemented Alerting health status pusher by using task manager and status pooler for Kibana status plugins 'kibanahost/api/status' * Exposed health task registration to alerts plugin * Fixed type error * Extended health API endpoint with info about decryption failures, added correct health task implementation * adjusted query * Tested locally and got it working as expected, fixed tests and type check * Added unit tests * Changed AlertExecutionStatusErrorReasons to be enum * Uppercase the enum * Replaced string values to enum * Fixed types * Extended AlertsClient with getHealth method * added return type to healthStatus$ * Added configurable health check interval and timestamps * Extended update core status interval to 5mins * Fixed failing tests * Registered alerts config * Fixed date for ok health state * fixed jest test * fixed task state * Fixed due to comments, moved getHealth to a plugin level * fixed type checks * Added sorting to the latest Ok state last update * adjusted error queries * Fixed jest tests * removed unused * fixed type check --- x-pack/plugins/alerts/common/alert.ts | 34 ++- x-pack/plugins/alerts/common/index.ts | 3 + x-pack/plugins/alerts/server/config.test.ts | 19 ++ x-pack/plugins/alerts/server/config.ts | 16 ++ .../alerts/server/health/get_health.test.ts | 221 ++++++++++++++++++ .../alerts/server/health/get_health.ts | 97 ++++++++ .../alerts/server/health/get_state.test.ts | 75 ++++++ .../plugins/alerts/server/health/get_state.ts | 73 ++++++ x-pack/plugins/alerts/server/health/index.ts | 8 + x-pack/plugins/alerts/server/health/task.ts | 94 ++++++++ x-pack/plugins/alerts/server/index.ts | 8 +- .../server/lib/alert_execution_status.test.ts | 8 +- .../server/lib/error_with_reason.test.ts | 7 +- .../alerts/server/lib/error_with_reason.ts | 2 +- .../lib/is_alert_not_found_error.test.ts | 3 +- x-pack/plugins/alerts/server/mocks.ts | 1 + x-pack/plugins/alerts/server/plugin.test.ts | 24 +- x-pack/plugins/alerts/server/plugin.ts | 40 +++- .../server/routes/_mock_handler_arguments.ts | 6 +- .../alerts/server/routes/health.test.ts | 215 +++++++++++++---- x-pack/plugins/alerts/server/routes/health.ts | 6 + .../alerts/server/task_runner/task_runner.ts | 7 +- x-pack/plugins/alerts/server/types.ts | 8 + .../rules/find_rules_status_route.test.ts | 3 +- .../lib/detection_engine/routes/utils.test.ts | 3 +- .../components/alert_details.test.tsx | 7 +- .../components/alerts_list.test.tsx | 7 +- .../tests/alerting/execution_status.ts | 3 +- 28 files changed, 917 insertions(+), 81 deletions(-) create mode 100644 x-pack/plugins/alerts/server/config.test.ts create mode 100644 x-pack/plugins/alerts/server/config.ts create mode 100644 x-pack/plugins/alerts/server/health/get_health.test.ts create mode 100644 x-pack/plugins/alerts/server/health/get_health.ts create mode 100644 x-pack/plugins/alerts/server/health/get_state.test.ts create mode 100644 x-pack/plugins/alerts/server/health/get_state.ts create mode 100644 x-pack/plugins/alerts/server/health/index.ts create mode 100644 x-pack/plugins/alerts/server/health/task.ts diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index 79e6bb8f2cbbaf..97a9a58400e385 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -20,13 +20,12 @@ export interface IntervalSchedule extends SavedObjectAttributes { export const AlertExecutionStatusValues = ['ok', 'active', 'error', 'pending', 'unknown'] as const; export type AlertExecutionStatuses = typeof AlertExecutionStatusValues[number]; -export const AlertExecutionStatusErrorReasonValues = [ - 'read', - 'decrypt', - 'execute', - 'unknown', -] as const; -export type AlertExecutionStatusErrorReasons = typeof AlertExecutionStatusErrorReasonValues[number]; +export enum AlertExecutionStatusErrorReasons { + Read = 'read', + Decrypt = 'decrypt', + Execute = 'execute', + Unknown = 'unknown', +} export interface AlertExecutionStatus { status: AlertExecutionStatuses; @@ -74,3 +73,24 @@ export interface Alert { } export type SanitizedAlert = Omit; + +export enum HealthStatus { + OK = 'ok', + Warning = 'warn', + Error = 'error', +} + +export interface AlertsHealth { + decryptionHealth: { + status: HealthStatus; + timestamp: string; + }; + executionHealth: { + status: HealthStatus; + timestamp: string; + }; + readHealth: { + status: HealthStatus; + timestamp: string; + }; +} diff --git a/x-pack/plugins/alerts/common/index.ts b/x-pack/plugins/alerts/common/index.ts index ab71f77a049f66..65aeec840da7e2 100644 --- a/x-pack/plugins/alerts/common/index.ts +++ b/x-pack/plugins/alerts/common/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertsHealth } from './alert'; + export * from './alert'; export * from './alert_type'; export * from './alert_instance'; @@ -19,6 +21,7 @@ export interface ActionGroup { export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; hasPermanentEncryptionKey: boolean; + alertingFrameworkHeath: AlertsHealth; } export const BASE_ALERT_API_PATH = '/api/alerts'; diff --git a/x-pack/plugins/alerts/server/config.test.ts b/x-pack/plugins/alerts/server/config.test.ts new file mode 100644 index 00000000000000..93aa3c38a04602 --- /dev/null +++ b/x-pack/plugins/alerts/server/config.test.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { configSchema } from './config'; + +describe('config validation', () => { + test('alerts defaults', () => { + const config: Record = {}; + expect(configSchema.validate(config)).toMatchInlineSnapshot(` + Object { + "healthCheck": Object { + "interval": "60m", + }, + } + `); + }); +}); diff --git a/x-pack/plugins/alerts/server/config.ts b/x-pack/plugins/alerts/server/config.ts new file mode 100644 index 00000000000000..a6d2196a407b5e --- /dev/null +++ b/x-pack/plugins/alerts/server/config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { validateDurationSchema } from './lib'; + +export const configSchema = schema.object({ + healthCheck: schema.object({ + interval: schema.string({ validate: validateDurationSchema, defaultValue: '60m' }), + }), +}); + +export type AlertsConfig = TypeOf; diff --git a/x-pack/plugins/alerts/server/health/get_health.test.ts b/x-pack/plugins/alerts/server/health/get_health.test.ts new file mode 100644 index 00000000000000..34517a89f04d92 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_health.test.ts @@ -0,0 +1,221 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { savedObjectsRepositoryMock } from '../../../../../src/core/server/mocks'; +import { AlertExecutionStatusErrorReasons, HealthStatus } from '../types'; +import { getHealth } from './get_health'; + +const savedObjectsRepository = savedObjectsRepositoryMock.create(); + +describe('getHealth()', () => { + test('return true if some of alerts has a decryption error', async () => { + const lastExecutionDateError = new Date().toISOString(); + const lastExecutionDate = new Date().toISOString(); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + executionStatus: { + status: 'error', + lastExecutionDate: lastExecutionDateError, + error: { + reason: AlertExecutionStatusErrorReasons.Decrypt, + message: 'Failed decrypt', + }, + }, + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + ], + }); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '2', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '1s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [], + executionStatus: { + status: 'ok', + lastExecutionDate, + }, + }, + score: 1, + references: [], + }, + ], + }); + const result = await getHealth(savedObjectsRepository); + expect(result).toStrictEqual({ + executionHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + decryptionHealth: { + status: HealthStatus.Warning, + timestamp: lastExecutionDateError, + }, + }); + expect(savedObjectsRepository.find).toHaveBeenCalledTimes(4); + }); + + test('return false if no alerts with a decryption error', async () => { + const lastExecutionDateError = new Date().toISOString(); + const lastExecutionDate = new Date().toISOString(); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + executionStatus: { + status: 'error', + lastExecutionDate: lastExecutionDateError, + error: { + reason: AlertExecutionStatusErrorReasons.Execute, + message: 'Failed', + }, + }, + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + ], + }); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '2', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '1s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [], + executionStatus: { + status: 'ok', + lastExecutionDate, + }, + }, + score: 1, + references: [], + }, + ], + }); + const result = await getHealth(savedObjectsRepository); + expect(result).toStrictEqual({ + executionHealth: { + status: HealthStatus.Warning, + timestamp: lastExecutionDateError, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + decryptionHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + }); + }); +}); diff --git a/x-pack/plugins/alerts/server/health/get_health.ts b/x-pack/plugins/alerts/server/health/get_health.ts new file mode 100644 index 00000000000000..b7b4582aa8d109 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_health.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { ISavedObjectsRepository } from 'src/core/server'; +import { AlertsHealth, HealthStatus, RawAlert, AlertExecutionStatusErrorReasons } from '../types'; + +export const getHealth = async ( + internalSavedObjectsRepository: ISavedObjectsRepository +): Promise => { + const healthStatuses = { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: '', + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: '', + }, + readHealth: { + status: HealthStatus.OK, + timestamp: '', + }, + }; + + const { saved_objects: decryptErrorData } = await internalSavedObjectsRepository.find({ + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Decrypt}`, + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + page: 1, + perPage: 1, + }); + + if (decryptErrorData.length > 0) { + healthStatuses.decryptionHealth = { + status: HealthStatus.Warning, + timestamp: decryptErrorData[0].attributes.executionStatus.lastExecutionDate, + }; + } + + const { saved_objects: executeErrorData } = await internalSavedObjectsRepository.find({ + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Execute}`, + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + page: 1, + perPage: 1, + }); + + if (executeErrorData.length > 0) { + healthStatuses.executionHealth = { + status: HealthStatus.Warning, + timestamp: executeErrorData[0].attributes.executionStatus.lastExecutionDate, + }; + } + + const { saved_objects: readErrorData } = await internalSavedObjectsRepository.find({ + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Read}`, + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + page: 1, + perPage: 1, + }); + + if (readErrorData.length > 0) { + healthStatuses.readHealth = { + status: HealthStatus.Warning, + timestamp: readErrorData[0].attributes.executionStatus.lastExecutionDate, + }; + } + + const { saved_objects: noErrorData } = await internalSavedObjectsRepository.find({ + filter: 'not alert.attributes.executionStatus.status:error', + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + }); + const lastExecutionDate = + noErrorData.length > 0 + ? noErrorData[0].attributes.executionStatus.lastExecutionDate + : new Date().toISOString(); + + for (const [, statusItem] of Object.entries(healthStatuses)) { + if (statusItem.status === HealthStatus.OK) { + statusItem.timestamp = lastExecutionDate; + } + } + + return healthStatuses; +}; diff --git a/x-pack/plugins/alerts/server/health/get_state.test.ts b/x-pack/plugins/alerts/server/health/get_state.test.ts new file mode 100644 index 00000000000000..86981c486da0f5 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_state.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { taskManagerMock } from '../../../task_manager/server/mocks'; +import { getHealthStatusStream } from '.'; +import { TaskStatus } from '../../../task_manager/server'; +import { HealthStatus } from '../types'; + +describe('getHealthStatusStream()', () => { + const mockTaskManager = taskManagerMock.createStart(); + + it('should return an object with the "unavailable" level and proper summary of "Alerting framework is unhealthy"', async () => { + mockTaskManager.get.mockReturnValue( + new Promise((_resolve, _reject) => { + return { + id: 'test', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + runs: 1, + health_status: HealthStatus.Warning, + }, + taskType: 'alerting:alerting_health_check', + params: { + alertId: '1', + }, + ownerId: null, + }; + }) + ); + getHealthStatusStream(mockTaskManager).subscribe( + (val: { level: Readonly; summary: string }) => { + expect(val.level).toBe(false); + } + ); + }); + + it('should return an object with the "available" level and proper summary of "Alerting framework is healthy"', async () => { + mockTaskManager.get.mockReturnValue( + new Promise((_resolve, _reject) => { + return { + id: 'test', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + runs: 1, + health_status: HealthStatus.OK, + }, + taskType: 'alerting:alerting_health_check', + params: { + alertId: '1', + }, + ownerId: null, + }; + }) + ); + getHealthStatusStream(mockTaskManager).subscribe( + (val: { level: Readonly; summary: string }) => { + expect(val.level).toBe(true); + } + ); + }); +}); diff --git a/x-pack/plugins/alerts/server/health/get_state.ts b/x-pack/plugins/alerts/server/health/get_state.ts new file mode 100644 index 00000000000000..476456ecad88ae --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_state.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { interval, Observable } from 'rxjs'; +import { catchError, switchMap } from 'rxjs/operators'; +import { ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server'; +import { TaskManagerStartContract } from '../../../task_manager/server'; +import { HEALTH_TASK_ID } from './task'; +import { HealthStatus } from '../types'; + +async function getLatestTaskState(taskManager: TaskManagerStartContract) { + try { + const result = await taskManager.get(HEALTH_TASK_ID); + return result; + } catch (err) { + const errMessage = err && err.message ? err.message : err.toString(); + if (!errMessage.includes('NotInitialized')) { + throw err; + } + } + + return null; +} + +const LEVEL_SUMMARY = { + [ServiceStatusLevels.available.toString()]: i18n.translate( + 'xpack.alerts.server.healthStatus.available', + { + defaultMessage: 'Alerting framework is available', + } + ), + [ServiceStatusLevels.degraded.toString()]: i18n.translate( + 'xpack.alerts.server.healthStatus.degraded', + { + defaultMessage: 'Alerting framework is degraded', + } + ), + [ServiceStatusLevels.unavailable.toString()]: i18n.translate( + 'xpack.alerts.server.healthStatus.unavailable', + { + defaultMessage: 'Alerting framework is unavailable', + } + ), +}; + +export const getHealthStatusStream = ( + taskManager: TaskManagerStartContract +): Observable> => { + return interval(60000 * 5).pipe( + switchMap(async () => { + const doc = await getLatestTaskState(taskManager); + const level = + doc?.state?.health_status === HealthStatus.OK + ? ServiceStatusLevels.available + : doc?.state?.health_status === HealthStatus.Warning + ? ServiceStatusLevels.degraded + : ServiceStatusLevels.unavailable; + return { + level, + summary: LEVEL_SUMMARY[level.toString()], + }; + }), + catchError(async (error) => ({ + level: ServiceStatusLevels.unavailable, + summary: LEVEL_SUMMARY[ServiceStatusLevels.unavailable.toString()], + meta: { error }, + })) + ); +}; diff --git a/x-pack/plugins/alerts/server/health/index.ts b/x-pack/plugins/alerts/server/health/index.ts new file mode 100644 index 00000000000000..730c4596aa550e --- /dev/null +++ b/x-pack/plugins/alerts/server/health/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { getHealthStatusStream } from './get_state'; +export { scheduleAlertingHealthCheck, initializeAlertingHealth } from './task'; diff --git a/x-pack/plugins/alerts/server/health/task.ts b/x-pack/plugins/alerts/server/health/task.ts new file mode 100644 index 00000000000000..6ea01a1083c130 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/task.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, Logger } from 'kibana/server'; +import { + RunContext, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../task_manager/server'; +import { AlertsConfig } from '../config'; +import { AlertingPluginsStart } from '../plugin'; +import { HealthStatus } from '../types'; +import { getHealth } from './get_health'; + +export const HEALTH_TASK_TYPE = 'alerting_health_check'; + +export const HEALTH_TASK_ID = `Alerting-${HEALTH_TASK_TYPE}`; + +export function initializeAlertingHealth( + logger: Logger, + taskManager: TaskManagerSetupContract, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]> +) { + registerAlertingHealthCheckTask(logger, taskManager, coreStartServices); +} + +export async function scheduleAlertingHealthCheck( + logger: Logger, + config: Promise, + taskManager: TaskManagerStartContract +) { + try { + const interval = (await config).healthCheck.interval; + await taskManager.ensureScheduled({ + id: HEALTH_TASK_ID, + taskType: HEALTH_TASK_TYPE, + schedule: { + interval, + }, + state: {}, + params: {}, + }); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); + } +} + +function registerAlertingHealthCheckTask( + logger: Logger, + taskManager: TaskManagerSetupContract, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]> +) { + taskManager.registerTaskDefinitions({ + [HEALTH_TASK_TYPE]: { + title: 'Alerting framework health check task', + createTaskRunner: healthCheckTaskRunner(logger, coreStartServices), + }, + }); +} + +export function healthCheckTaskRunner( + logger: Logger, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]> +) { + return ({ taskInstance }: RunContext) => { + const { state } = taskInstance; + return { + async run() { + try { + const alertingHealthStatus = await getHealth( + (await coreStartServices)[0].savedObjects.createInternalRepository(['alert']) + ); + return { + state: { + runs: (state.runs || 0) + 1, + health_status: alertingHealthStatus.decryptionHealth.status, + }, + }; + } catch (errMsg) { + logger.warn(`Error executing alerting health check task: ${errMsg}`); + return { + state: { + runs: (state.runs || 0) + 1, + health_status: HealthStatus.Error, + }, + }; + } + }, + }; + }; +} diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts index 1e442c5196cf20..64e585da5c6544 100644 --- a/x-pack/plugins/alerts/server/index.ts +++ b/x-pack/plugins/alerts/server/index.ts @@ -5,8 +5,10 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertsClient as AlertsClientClass } from './alerts_client'; -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server'; import { AlertingPlugin } from './plugin'; +import { configSchema } from './config'; +import { AlertsConfigType } from './types'; export type AlertsClient = PublicMethodsOf; @@ -30,3 +32,7 @@ export { AlertInstance } from './alert_instance'; export { parseDuration } from './lib'; export const plugin = (initContext: PluginInitializerContext) => new AlertingPlugin(initContext); + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts b/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts index 3372d19cd40901..bb24ab034d0dde 100644 --- a/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts @@ -57,7 +57,9 @@ describe('AlertExecutionStatus', () => { }); test('error with a reason', () => { - const status = executionStatusFromError(new ErrorWithReason('execute', new Error('hoo!'))); + const status = executionStatusFromError( + new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, new Error('hoo!')) + ); expect(status.status).toBe('error'); expect(status.error).toMatchInlineSnapshot(` Object { @@ -71,7 +73,7 @@ describe('AlertExecutionStatus', () => { describe('alertExecutionStatusToRaw()', () => { const date = new Date('2020-09-03T16:26:58Z'); const status = 'ok'; - const reason: AlertExecutionStatusErrorReasons = 'decrypt'; + const reason = AlertExecutionStatusErrorReasons.Decrypt; const error = { reason, message: 'wops' }; test('status without an error', () => { @@ -102,7 +104,7 @@ describe('AlertExecutionStatus', () => { describe('alertExecutionStatusFromRaw()', () => { const date = new Date('2020-09-03T16:26:58Z').toISOString(); const status = 'active'; - const reason: AlertExecutionStatusErrorReasons = 'execute'; + const reason = AlertExecutionStatusErrorReasons.Execute; const error = { reason, message: 'wops' }; test('no input', () => { diff --git a/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts b/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts index f31f5844003086..eff935966345f9 100644 --- a/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts +++ b/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts @@ -5,20 +5,21 @@ */ import { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; +import { AlertExecutionStatusErrorReasons } from '../types'; describe('ErrorWithReason', () => { const plainError = new Error('well, actually'); - const errorWithReason = new ErrorWithReason('decrypt', plainError); + const errorWithReason = new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, plainError); test('ErrorWithReason class', () => { expect(errorWithReason.message).toBe(plainError.message); expect(errorWithReason.error).toBe(plainError); - expect(errorWithReason.reason).toBe('decrypt'); + expect(errorWithReason.reason).toBe(AlertExecutionStatusErrorReasons.Decrypt); }); test('getReasonFromError()', () => { expect(getReasonFromError(plainError)).toBe('unknown'); - expect(getReasonFromError(errorWithReason)).toBe('decrypt'); + expect(getReasonFromError(errorWithReason)).toBe(AlertExecutionStatusErrorReasons.Decrypt); }); test('isErrorWithReason()', () => { diff --git a/x-pack/plugins/alerts/server/lib/error_with_reason.ts b/x-pack/plugins/alerts/server/lib/error_with_reason.ts index 29eb666e644272..a732b44ef2238c 100644 --- a/x-pack/plugins/alerts/server/lib/error_with_reason.ts +++ b/x-pack/plugins/alerts/server/lib/error_with_reason.ts @@ -21,7 +21,7 @@ export function getReasonFromError(error: Error): AlertExecutionStatusErrorReaso if (isErrorWithReason(error)) { return error.reason; } - return 'unknown'; + return AlertExecutionStatusErrorReasons.Unknown; } export function isErrorWithReason(error: Error | ErrorWithReason): error is ErrorWithReason { diff --git a/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts index b570957d82de4a..ab21dc77fa251c 100644 --- a/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts +++ b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts @@ -8,6 +8,7 @@ import { isAlertSavedObjectNotFoundError } from './is_alert_not_found_error'; import { ErrorWithReason } from './error_with_reason'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import uuid from 'uuid'; +import { AlertExecutionStatusErrorReasons } from '../types'; describe('isAlertSavedObjectNotFoundError', () => { const id = uuid.v4(); @@ -25,7 +26,7 @@ describe('isAlertSavedObjectNotFoundError', () => { }); test('identifies SavedObjects Not Found errors wrapped in an ErrorWithReason', () => { - const error = new ErrorWithReason('read', errorSONF); + const error = new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, errorSONF); expect(isAlertSavedObjectNotFoundError(error, id)).toBe(true); }); }); diff --git a/x-pack/plugins/alerts/server/mocks.ts b/x-pack/plugins/alerts/server/mocks.ts index 05d64bdbb77f4f..cfae4c650bd42c 100644 --- a/x-pack/plugins/alerts/server/mocks.ts +++ b/x-pack/plugins/alerts/server/mocks.ts @@ -25,6 +25,7 @@ const createStartMock = () => { const mock: jest.Mocked = { listTypes: jest.fn(), getAlertsClientWithRequest: jest.fn().mockResolvedValue(alertsClientMock.create()), + getFrameworkHealth: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index b13a1c62f66022..715fbc6aeed459 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -5,7 +5,7 @@ */ import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin'; -import { coreMock } from '../../../../src/core/server/mocks'; +import { coreMock, statusServiceMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; @@ -13,15 +13,21 @@ import { eventLogServiceMock } from '../../event_log/server/event_log_service.mo import { KibanaRequest, CoreSetup } from 'kibana/server'; import { featuresPluginMock } from '../../features/server/mocks'; import { KibanaFeature } from '../../features/server'; +import { AlertsConfig } from './config'; describe('Alerting Plugin', () => { describe('setup()', () => { it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + }); const plugin = new AlertingPlugin(context); const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); + const statusMock = statusServiceMock.createSetupContract(); await plugin.setup( ({ ...coreSetup, @@ -29,6 +35,7 @@ describe('Alerting Plugin', () => { ...coreSetup.http, route: jest.fn(), }, + status: statusMock, } as unknown) as CoreSetup, ({ licensing: licensingMock.createSetup(), @@ -38,6 +45,7 @@ describe('Alerting Plugin', () => { } as unknown) as AlertingPluginsSetup ); + expect(statusMock.set).toHaveBeenCalledTimes(1); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(context.logger.get().warn).toHaveBeenCalledWith( 'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.' @@ -55,7 +63,11 @@ describe('Alerting Plugin', () => { */ describe('getAlertsClientWithRequest()', () => { it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + }); const plugin = new AlertingPlugin(context); const coreSetup = coreMock.createSetup(); @@ -98,7 +110,11 @@ describe('Alerting Plugin', () => { }); it(`doesn't throw error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to false`, async () => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + }); const plugin = new AlertingPlugin(context); const coreSetup = coreMock.createSetup(); diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 75873a2845c15e..1fa89606a76fc7 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -6,6 +6,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { first, map } from 'rxjs/operators'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { combineLatest } from 'rxjs'; import { SecurityPluginSetup } from '../../security/server'; import { EncryptedSavedObjectsPluginSetup, @@ -30,6 +31,8 @@ import { SharedGlobalConfig, ElasticsearchServiceStart, ILegacyClusterClient, + StatusServiceSetup, + ServiceStatus, } from '../../../../src/core/server'; import { @@ -56,12 +59,19 @@ import { PluginSetupContract as ActionsPluginSetupContract, PluginStartContract as ActionsPluginStartContract, } from '../../actions/server'; -import { Services } from './types'; +import { AlertsHealth, Services } from './types'; import { registerAlertsUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; import { IEventLogger, IEventLogService, IEventLogClientService } from '../../event_log/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; import { setupSavedObjects } from './saved_objects'; +import { + getHealthStatusStream, + scheduleAlertingHealthCheck, + initializeAlertingHealth, +} from './health'; +import { AlertsConfig } from './config'; +import { getHealth } from './health/get_health'; export const EVENT_LOG_PROVIDER = 'alerting'; export const EVENT_LOG_ACTIONS = { @@ -78,6 +88,7 @@ export interface PluginSetupContract { export interface PluginStartContract { listTypes: AlertTypeRegistry['list']; getAlertsClientWithRequest(request: KibanaRequest): PublicMethodsOf; + getFrameworkHealth: () => Promise; } export interface AlertingPluginsSetup { @@ -89,6 +100,7 @@ export interface AlertingPluginsSetup { spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; eventLog: IEventLogService; + statusService: StatusServiceSetup; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; @@ -99,6 +111,7 @@ export interface AlertingPluginsStart { } export class AlertingPlugin { + private readonly config: Promise; private readonly logger: Logger; private alertTypeRegistry?: AlertTypeRegistry; private readonly taskRunnerFactory: TaskRunnerFactory; @@ -115,6 +128,7 @@ export class AlertingPlugin { private eventLogger?: IEventLogger; constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.create().pipe(first()).toPromise(); this.logger = initializerContext.logger.get('plugins', 'alerting'); this.taskRunnerFactory = new TaskRunnerFactory(); this.alertsClientFactory = new AlertsClientFactory(); @@ -186,6 +200,25 @@ export class AlertingPlugin { }); } + core.getStartServices().then(async ([, startPlugins]) => { + core.status.set( + combineLatest([ + core.status.derivedStatus$, + getHealthStatusStream(startPlugins.taskManager), + ]).pipe( + map(([derivedStatus, healthStatus]) => { + if (healthStatus.level > derivedStatus.level) { + return healthStatus as ServiceStatus; + } else { + return derivedStatus; + } + }) + ) + ); + }); + + initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices()); + core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core)); // Routes @@ -275,10 +308,13 @@ export class AlertingPlugin { }); scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); + scheduleAlertingHealthCheck(this.logger, this.config, plugins.taskManager); return { listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!), getAlertsClientWithRequest, + getFrameworkHealth: async () => + await getHealth(core.savedObjects.createInternalRepository(['alert'])), }; } @@ -293,6 +329,8 @@ export class AlertingPlugin { return alertsClientFactory!.create(request, savedObjects); }, listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!), + getFrameworkHealth: async () => + await getHealth(savedObjects.createInternalRepository(['alert'])), }; }; }; diff --git a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts index 3d13fc65ab260e..b3f407b20c142c 100644 --- a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts @@ -14,7 +14,7 @@ import { identity } from 'lodash'; import type { MethodKeysOf } from '@kbn/utility-types'; import { httpServerMock } from '../../../../../src/core/server/mocks'; import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock'; -import { AlertType } from '../../common'; +import { AlertsHealth, AlertType } from '../../common'; import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; export function mockHandlerArguments( @@ -22,10 +22,13 @@ export function mockHandlerArguments( alertsClient = alertsClientMock.create(), listTypes: listTypesRes = [], esClient = elasticsearchServiceMock.createLegacyClusterClient(), + getFrameworkHealth, }: { alertsClient?: AlertsClientMock; listTypes?: AlertType[]; esClient?: jest.Mocked; + getFrameworkHealth?: jest.MockInstance, []> & + (() => Promise); }, req: unknown, res?: Array> @@ -39,6 +42,7 @@ export function mockHandlerArguments( getAlertsClient() { return alertsClient || alertsClientMock.create(); }, + getFrameworkHealth, }, } as unknown) as RequestHandlerContext, req as KibanaRequest, diff --git a/x-pack/plugins/alerts/server/routes/health.test.ts b/x-pack/plugins/alerts/server/routes/health.test.ts index ce782dbd631a5a..d1967c6dd9bf84 100644 --- a/x-pack/plugins/alerts/server/routes/health.test.ts +++ b/x-pack/plugins/alerts/server/routes/health.test.ts @@ -11,13 +11,34 @@ import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { verifyApiAccess } from '../lib/license_api_access'; import { mockLicenseState } from '../lib/license_state.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; +import { alertsClientMock } from '../alerts_client.mock'; +import { HealthStatus } from '../types'; +import { alertsMock } from '../mocks'; +const alertsClient = alertsClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); +const alerting = alertsMock.createStart(); + +const currentDate = new Date().toISOString(); beforeEach(() => { jest.resetAllMocks(); + alerting.getFrameworkHealth.mockResolvedValue({ + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }); }); describe('healthRoute', () => { @@ -46,7 +67,7 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient, alertsClient }, {}, ['ok']); await handler(context, req, res); @@ -75,16 +96,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": false, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: false, + isSufficientlySecure: true, + }, + }); }); it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => { @@ -99,16 +136,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); }); it('evaluates missing security http info from the usage api to mean that the security plugin is disbled', async () => { @@ -123,16 +176,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: {} })); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); }); it('evaluates security enabled, and missing ssl info from the usage api to mean that the user cannot generate keys', async () => { @@ -147,16 +216,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: { enabled: true } })); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": false, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: false, + }, + }); }); it('evaluates security enabled, SSL info present but missing http info from the usage api to mean that the user cannot generate keys', async () => { @@ -173,16 +258,32 @@ describe('healthRoute', () => { Promise.resolve({ security: { enabled: true, ssl: {} } }) ); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": false, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: false, + }, + }); }); it('evaluates security and tls enabled to mean that the user can generate keys', async () => { @@ -199,15 +300,31 @@ describe('healthRoute', () => { Promise.resolve({ security: { enabled: true, ssl: { http: { enabled: true } } } }) ); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); }); }); diff --git a/x-pack/plugins/alerts/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts index b66e28b24e8a74..bfd5b1e2722878 100644 --- a/x-pack/plugins/alerts/server/routes/health.ts +++ b/x-pack/plugins/alerts/server/routes/health.ts @@ -43,6 +43,9 @@ export function healthRoute( res: KibanaResponseFactory ): Promise { verifyApiAccess(licenseState); + if (!context.alerting) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); + } try { const { security: { @@ -57,9 +60,12 @@ export function healthRoute( path: '/_xpack/usage', }); + const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); + const frameworkHealth: AlertingFrameworkHealth = { isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey, + alertingFrameworkHeath, }; return res.ok({ diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 6a49f67268d697..86bf7006e8d09e 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -28,6 +28,7 @@ import { AlertExecutorOptions, SanitizedAlert, AlertExecutionStatus, + AlertExecutionStatusErrorReasons, } from '../types'; import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type'; import { taskInstanceToAlertTaskInstance } from './alert_task_instance'; @@ -211,7 +212,7 @@ export class TaskRunner { event.event = event.event || {}; event.event.outcome = 'failure'; eventLogger.logEvent(event); - throw new ErrorWithReason('execute', err); + throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, err); } eventLogger.stopTiming(event); @@ -288,7 +289,7 @@ export class TaskRunner { try { apiKey = await this.getApiKeyForAlertPermissions(alertId, spaceId); } catch (err) { - throw new ErrorWithReason('decrypt', err); + throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, err); } const [services, alertsClient] = this.getServicesWithSpaceLevelPermissions(spaceId, apiKey); @@ -298,7 +299,7 @@ export class TaskRunner { try { alert = await alertsClient.get({ id: alertId }); } catch (err) { - throw new ErrorWithReason('read', err); + throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, err); } return { diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 42eef9bba10e52..9226461f6e30a4 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -27,6 +27,7 @@ import { AlertInstanceState, AlertExecutionStatuses, AlertExecutionStatusErrorReasons, + AlertsHealth, } from '../common'; export type WithoutQueryAndParams = Pick>; @@ -39,6 +40,7 @@ declare module 'src/core/server' { alerting?: { getAlertsClient: () => AlertsClient; listTypes: AlertTypeRegistry['list']; + getFrameworkHealth: () => Promise; }; } } @@ -172,4 +174,10 @@ export interface AlertingPlugin { start: PluginStartContract; } +export interface AlertsConfigType { + healthCheck: { + interval: string; + }; +} + export type AlertTypeRegistry = PublicMethodsOf; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts index 4d992c6c7029d0..4b75127af1bc76 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts @@ -9,6 +9,7 @@ import { getFindResultStatus, ruleStatusRequest, getResult } from '../__mocks__/ import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { findRulesStatusesRoute } from './find_rules_status_route'; import { RuleStatusResponse } from '../../rules/types'; +import { AlertExecutionStatusErrorReasons } from '../../../../../../alerts/common'; jest.mock('../../signals/rule_status_service'); @@ -57,7 +58,7 @@ describe('find_statuses', () => { status: 'error', lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate, error: { - reason: 'read', + reason: AlertExecutionStatusErrorReasons.Read, message: 'oops', }, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index 25e47b38e8a564..b613061ac85f2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -27,6 +27,7 @@ import { import { responseMock } from './__mocks__'; import { exampleRuleStatus, exampleFindRuleStatusResponse } from '../signals/__mocks__/es_results'; import { getResult } from './__mocks__/request_responses'; +import { AlertExecutionStatusErrorReasons } from '../../../../../alerts/common'; let alertsClient: ReturnType; @@ -464,7 +465,7 @@ describe('utils', () => { status: 'error', lastExecutionDate: foundRule.executionStatus.lastExecutionDate, error: { - reason: 'read', + reason: AlertExecutionStatusErrorReasons.Read, message: 'oops', }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 662db81101eeee..70b6fb0b750dd5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -11,7 +11,10 @@ import { Alert, ActionType, ValidationResult } from '../../../../types'; import { EuiTitle, EuiBadge, EuiFlexItem, EuiSwitch, EuiButtonEmpty, EuiText } from '@elastic/eui'; import { ViewInApp } from './view_in_app'; import { coreMock } from 'src/core/public/mocks'; -import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; +import { + AlertExecutionStatusErrorReasons, + ALERTS_FEATURE_ID, +} from '../../../../../../alerts/common'; const mockes = coreMock.createSetup(); @@ -125,7 +128,7 @@ describe('alert_details', () => { status: 'error', lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: 'unknown', + reason: AlertExecutionStatusErrorReasons.Unknown, message: 'test', }, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 18cc7b540296ed..c434ca9d214028 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -17,7 +17,10 @@ import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; -import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; +import { + AlertExecutionStatusErrorReasons, + ALERTS_FEATURE_ID, +} from '../../../../../../alerts/common'; import { featuresPluginMock } from '../../../../../../features/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ @@ -245,7 +248,7 @@ describe('alerts_list component with items', () => { status: 'error', lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: 'unknown', + reason: AlertExecutionStatusErrorReasons.Unknown, message: 'test', }, }, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts index 8fb89042e4a903..4058b71356280a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { AlertExecutionStatusErrorReasons } from '../../../../../plugins/alerts/common'; import { Spaces } from '../../scenarios'; import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -49,7 +50,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); - expect(executionStatus.error.reason).to.be('decrypt'); + expect(executionStatus.error.reason).to.be(AlertExecutionStatusErrorReasons.Decrypt); expect(executionStatus.error.message).to.be('Unable to decrypt attribute "apiKey"'); }); }); From fb8cd5b0916327952e6d053bc19d7c2ea9ebf77a Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 6 Nov 2020 20:46:52 -0700 Subject: [PATCH 47/81] [Security Solution] Adds logging and performance fan out API for threat/Indicator matching (#82546) ## Summary * Adds logging output for trouble shooting * Adds an API to be able to configure how many concurrent searches and how many items per search to use API additions are these two switches: ``` concurrent_searches items_per_search ``` When you create a rule. You can use the following example to post one or to change the settings to see the performance impact: ```ts ./post_rule.sh ./rules/queries/query_with_threat_mapping_perf.json ``` Without using these two experimental API settings, the functionality is the same as the existing algorithm and only advanced users will be able to set the additional REST settings through this API. If you use the front end after setting the settings, the settings will be reset as that's how the forms code currently works and this will not preserve the settings if afterwards a rule is edited/changed. Both these API settings should be considered experimental and potentially breakable as we figure out the best performance strategies for indicator matching. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../request/add_prepackaged_rules_schema.ts | 4 + .../request/create_rules_schema.test.ts | 18 + .../schemas/request/create_rules_schema.ts | 4 + .../create_rules_type_dependents.test.ts | 32 ++ .../request/create_rules_type_dependents.ts | 12 +- .../schemas/request/import_rules_schema.ts | 4 + .../schemas/request/patch_rules_schema.ts | 4 + .../schemas/request/update_rules_schema.ts | 4 + .../schemas/response/rules_schema.test.ts | 4 +- .../schemas/response/rules_schema.ts | 10 + .../schemas/types/threat_mapping.test.ts | 58 ++- .../schemas/types/threat_mapping.ts | 11 + .../rules/step_define_rule/translations.tsx | 2 +- .../routes/__mocks__/request_responses.ts | 2 + .../routes/rules/create_rules_bulk_route.ts | 4 + .../routes/rules/create_rules_route.ts | 4 + .../routes/rules/import_rules_route.ts | 6 + .../routes/rules/patch_rules_bulk_route.ts | 4 + .../routes/rules/patch_rules_route.ts | 4 + .../routes/rules/update_rules_bulk_route.ts | 4 + .../routes/rules/update_rules_route.ts | 4 + .../detection_engine/routes/rules/utils.ts | 2 + .../rules/create_rules.mock.ts | 4 + .../detection_engine/rules/create_rules.ts | 4 + .../rules/install_prepacked_rules.ts | 4 + .../rules/patch_rules.mock.ts | 4 + .../lib/detection_engine/rules/patch_rules.ts | 6 + .../lib/detection_engine/rules/types.ts | 8 + .../rules/update_prepacked_rules.ts | 4 + .../rules/update_rules.mock.ts | 4 + .../detection_engine/rules/update_rules.ts | 4 + .../lib/detection_engine/rules/utils.test.ts | 6 + .../lib/detection_engine/rules/utils.ts | 4 + .../scripts/create_threat_data.sh | 4 +- .../query_with_threat_mapping_perf.json | 32 ++ .../signals/__mocks__/es_results.ts | 2 + .../signals/signal_params_schema.ts | 2 + .../signals/signal_rule_alert_type.test.ts | 2 +- .../signals/signal_rule_alert_type.ts | 6 +- .../build_threat_mapping_filter.test.ts | 69 ++-- .../build_threat_mapping_filter.ts | 41 +- .../threat_mapping/create_threat_signal.ts | 51 +-- .../threat_mapping/create_threat_signals.ts | 133 ++++-- .../signals/threat_mapping/get_threat_list.ts | 37 ++ .../signals/threat_mapping/types.ts | 31 +- .../signals/threat_mapping/utils.test.ts | 387 +++++++++++++++++- .../signals/threat_mapping/utils.ts | 71 ++++ .../server/lib/detection_engine/types.ts | 4 + 48 files changed, 981 insertions(+), 144 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts index 6ffbf4e4c8d4ca..1b0417cf59bc28 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts @@ -48,6 +48,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -130,6 +132,8 @@ export const addPrepackagedRulesSchema = t.intersection([ threat_query, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults to "undefined" if not set during decode + items_per_search, // defaults to "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts index a4f002b589ef55..1b6a8d6f27762e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts @@ -1702,5 +1702,23 @@ describe('create rules schema', () => { expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(expected); }); + + test('You can set a threat query, index, mapping, filters, concurrent_searches, items_per_search with a when creating a rule', () => { + const payload: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + concurrent_searches: 10, + items_per_search: 10, + }; + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected: CreateRulesSchemaDecoded = { + ...getCreateThreatMatchRulesSchemaDecodedMock(), + concurrent_searches: 10, + items_per_search: 10, + }; + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts index d8e7614fcb8401..2fe52bbe470a52 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts @@ -49,6 +49,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -126,6 +128,8 @@ export const createRulesSchema = t.intersection([ threat_filters, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults "undefined" if not set during decode + items_per_search, // defaults "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts index 75ad92578318ce..a78b41cd0da187 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts @@ -125,4 +125,36 @@ describe('create_rules_type_dependents', () => { const errors = createRuleValidateTypeDependents(schema); expect(errors).toEqual([]); }); + + test('validates that both "items_per_search" and "concurrent_searches" works when together', () => { + const schema: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + concurrent_searches: 10, + items_per_search: 10, + }; + const errors = createRuleValidateTypeDependents(schema); + expect(errors).toEqual([]); + }); + + test('does NOT validate when only "items_per_search" is present', () => { + const schema: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + items_per_search: 10, + }; + const errors = createRuleValidateTypeDependents(schema); + expect(errors).toEqual([ + 'when "items_per_search" exists, "concurrent_searches" must also exist', + ]); + }); + + test('does NOT validate when only "concurrent_searches" is present', () => { + const schema: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + concurrent_searches: 10, + }; + const errors = createRuleValidateTypeDependents(schema); + expect(errors).toEqual([ + 'when "concurrent_searches" exists, "items_per_search" must also exist', + ]); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts index c2a41005ebf4d6..c93b0f0b14f6ad 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts @@ -110,17 +110,23 @@ export const validateThreshold = (rule: CreateRulesSchema): string[] => { export const validateThreatMapping = (rule: CreateRulesSchema): string[] => { let errors: string[] = []; if (isThreatMatchRule(rule.type)) { - if (!rule.threat_mapping) { + if (rule.threat_mapping == null) { errors = ['when "type" is "threat_match", "threat_mapping" is required', ...errors]; } else if (rule.threat_mapping.length === 0) { errors = ['threat_mapping" must have at least one element', ...errors]; } - if (!rule.threat_query) { + if (rule.threat_query == null) { errors = ['when "type" is "threat_match", "threat_query" is required', ...errors]; } - if (!rule.threat_index) { + if (rule.threat_index == null) { errors = ['when "type" is "threat_match", "threat_index" is required', ...errors]; } + if (rule.concurrent_searches == null && rule.items_per_search != null) { + errors = ['when "items_per_search" exists, "concurrent_searches" must also exist', ...errors]; + } + if (rule.concurrent_searches != null && rule.items_per_search == null) { + errors = ['when "concurrent_searches" exists, "items_per_search" must also exist', ...errors]; + } } return errors; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts index 852394b74767b7..4f28c469238658 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts @@ -55,6 +55,8 @@ import { } from '../common/schemas'; import { threat_index, + items_per_search, + concurrent_searches, threat_query, threat_filters, threat_mapping, @@ -149,6 +151,8 @@ export const importRulesSchema = t.intersection([ threat_query, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults to "undefined" if not set during decode + items_per_search, // defaults to "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts index f4dce5c7ac05f8..45fcfbaa3c76aa 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts @@ -50,6 +50,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -109,6 +111,8 @@ export const patchRulesSchema = t.exact( threat_filters, threat_mapping, threat_language, + concurrent_searches, + items_per_search, }) ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts index b0cd8b1c53688a..5d759fc12cd528 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts @@ -51,6 +51,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -134,6 +136,8 @@ export const updateRulesSchema = t.intersection([ threat_filters, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults to "undefined" if not set during decode + items_per_search, // defaults to "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts index 82675768a11b7c..3508526e182d77 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -762,9 +762,9 @@ describe('rules_schema', () => { expect(fields).toEqual(expected); }); - test('should return 5 fields for a rule of type "threat_match"', () => { + test('should return 8 fields for a rule of type "threat_match"', () => { const fields = addThreatMatchFields({ type: 'threat_match' }); - expect(fields.length).toEqual(6); + expect(fields.length).toEqual(8); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts index e85beddf0e51e9..0f7d04763a36f1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts @@ -63,6 +63,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -144,6 +146,8 @@ export const dependentRulesSchema = t.partial({ threat_filters, threat_index, threat_query, + concurrent_searches, + items_per_search, threat_mapping, threat_language, }); @@ -282,6 +286,12 @@ export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })), t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })), t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), + t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })), + t.exact( + t.partial({ + items_per_search: dependentRulesSchema.props.items_per_search, + }) + ), ]; } else { return []; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts index 63d593ea84e67f..d8f61e4309b17a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts @@ -5,6 +5,8 @@ */ import { + concurrent_searches, + items_per_search, ThreatMapping, threatMappingEntries, ThreatMappingEntries, @@ -33,7 +35,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an extra entry item', () => { + test('it should fail validation with an extra entry item', () => { const payload: ThreatMappingEntries & Array<{ extra: string }> = [ { field: 'field.one', @@ -50,7 +52,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate a non string', () => { + test('it should fail validation with a non string', () => { const payload = ([ { field: 5, @@ -66,7 +68,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate a wrong type', () => { + test('it should fail validation with a wrong type', () => { const payload = ([ { field: 'field.one', @@ -107,7 +109,7 @@ describe('threat_mapping', () => { }); }); - test('it should NOT validate an extra key', () => { + test('it should fail validate with an extra key', () => { const payload: ThreatMapping & Array<{ extra: string }> = [ { entries: [ @@ -129,7 +131,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate an extra inner entry', () => { + test('it should fail validate with an extra inner entry', () => { const payload: ThreatMapping & Array<{ entries: Array<{ extra: string }> }> = [ { entries: [ @@ -151,7 +153,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate an extra inner entry with the wrong data type', () => { + test('it should fail validate with an extra inner entry with the wrong data type', () => { const payload = ([ { entries: [ @@ -173,4 +175,48 @@ describe('threat_mapping', () => { ]); expect(message.schema).toEqual({}); }); + + test('it should fail validation when concurrent_searches is < 0', () => { + const payload = -1; + const decoded = concurrent_searches.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-1" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when concurrent_searches is 0', () => { + const payload = 0; + const decoded = concurrent_searches.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when items_per_search is 0', () => { + const payload = 0; + const decoded = items_per_search.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when items_per_search is < 0', () => { + const payload = -1; + const decoded = items_per_search.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-1" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts index a1be6485f596b7..dec8ddd0001324 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { language } from '../common/schemas'; import { NonEmptyString } from './non_empty_string'; +import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; export const threat_query = t.string; export type ThreatQuery = t.TypeOf; @@ -55,3 +56,13 @@ export const threat_language = t.union([language, t.undefined]); export type ThreatLanguage = t.TypeOf; export const threatLanguageOrUndefined = t.union([threat_language, t.undefined]); export type ThreatLanguageOrUndefined = t.TypeOf; + +export const concurrent_searches = PositiveIntegerGreaterThanZero; +export type ConcurrentSearches = t.TypeOf; +export const concurrentSearchesOrUndefined = t.union([concurrent_searches, t.undefined]); +export type ConcurrentSearchesOrUndefined = t.TypeOf; + +export const items_per_search = PositiveIntegerGreaterThanZero; +export type ItemsPerSearch = t.TypeOf; +export const itemsPerSearchOrUndefined = t.union([items_per_search, t.undefined]); +export type ItemsPerSearchOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx index 164b1df8463e6f..221963767caade 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx @@ -95,7 +95,7 @@ export const THREAT_MATCH_INDEX_HELPER_TEXT = i18n.translate( export const THREAT_MATCH_REQUIRED = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredError', { - defaultMessage: 'At least one threat match is required.', + defaultMessage: 'At least one indicator match is required.', } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 94b820344b37ca..773e84d9c88fce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -407,6 +407,8 @@ export const getResult = (): RuleAlertType => ({ note: '# Investigative notes', version: 1, exceptionsList: getListArrayMock(), + concurrentSearches: undefined, + itemsPerSearch: undefined, }, createdAt: new Date('2019-12-13T16:40:33.400Z'), updatedAt: new Date('2019-12-13T16:40:33.400Z'), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 8c7a19869ce188..aa409580df9655 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -102,6 +102,8 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threat_mapping: threatMapping, threat_query: threatQuery, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, threshold, throttle, timestamp_override: timestampOverride, @@ -193,6 +195,8 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threatQuery, threatIndex, threatLanguage, + concurrentSearches, + itemsPerSearch, threshold, timestampOverride, references, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index 6ba7bc78fbded6..97c05b4626ddc2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -85,6 +85,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, throttle, timestamp_override: timestampOverride, to, @@ -182,6 +184,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index 7cbcf25590921c..688036c59c8ffe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -169,6 +169,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, threshold, timestamp_override: timestampOverride, to, @@ -235,6 +237,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, @@ -284,6 +288,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 4c310774ec72b2..7dfb4daa1a0a22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -97,6 +97,8 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, timestamp_override: timestampOverride, throttle, references, @@ -162,6 +164,8 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index dbdcd9844c0a79..aadb13ef54e726 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -83,6 +83,8 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, timestamp_override: timestampOverride, throttle, references, @@ -161,6 +163,8 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index b93b3f319193f4..f4a31c2bb456dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -102,6 +102,8 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, throttle, timestamp_override: timestampOverride, references, @@ -174,6 +176,8 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index ea19fed5d6668b..7ad525b67f7aa1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -86,6 +86,8 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, throttle, timestamp_override: timestampOverride, references, @@ -163,6 +165,8 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index fb4ba855f65369..7360dc77aac22c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -151,6 +151,8 @@ export const transformAlertToRule = ( threat_query: alert.params.threatQuery, threat_mapping: alert.params.threatMapping, threat_language: alert.params.threatLanguage, + concurrent_searches: alert.params.concurrentSearches, + items_per_search: alert.params.itemsPerSearch, throttle: ruleActions?.ruleThrottle || 'no_actions', timestamp_override: alert.params.timestampOverride, note: alert.params.note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts index 271b1043ea5683..68199c531a2fec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts @@ -43,6 +43,8 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ threatFilters: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, threatQuery: undefined, threatIndex: undefined, threshold: undefined, @@ -94,6 +96,8 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ threatMapping: undefined, threatQuery: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, threshold: undefined, timestampOverride: undefined, to: 'now', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 776882d0f84941..3c814ce7e66067 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -46,6 +46,8 @@ export const createRules = async ({ threatFilters, threatIndex, threatLanguage, + concurrentSearches, + itemsPerSearch, threatQuery, threatMapping, threshold, @@ -96,6 +98,8 @@ export const createRules = async ({ threatFilters, threatIndex, threatQuery, + concurrentSearches, + itemsPerSearch, threatMapping, threatLanguage, timestampOverride, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 0a43c652234d09..4c01318f02cdea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -51,6 +51,8 @@ export const installPrepackagedRules = ( threat_filters: threatFilters, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, threat_query: threatQuery, threat_index: threatIndex, threshold, @@ -103,6 +105,8 @@ export const installPrepackagedRules = ( threatFilters, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, threatQuery, threatIndex, threshold, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index ef7cd35f28f1bd..60f1d599470e3c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -154,6 +154,8 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, timestampOverride: undefined, to: 'now', type: 'query', @@ -203,6 +205,8 @@ export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, timestampOverride: undefined, to: 'now', type: 'machine_learning', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 1982dcf9dd9b67..22b2593283696d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -49,6 +49,8 @@ export const patchRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, @@ -97,6 +99,8 @@ export const patchRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, @@ -141,6 +145,8 @@ export const patchRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index fb4763a982f43f..f6ab3fb0c3ed2e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -92,6 +92,8 @@ import { ThreatMappingOrUndefined, ThreatFiltersOrUndefined, ThreatLanguageOrUndefined, + ConcurrentSearchesOrUndefined, + ItemsPerSearchOrUndefined, } from '../../../../common/detection_engine/schemas/types/threat_mapping'; import { AlertsClient, PartialAlert } from '../../../../../alerts/server'; @@ -234,6 +236,8 @@ export interface CreateRulesOptions { threatIndex: ThreatIndexOrUndefined; threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; threatLanguage: ThreatLanguageOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: To; @@ -284,6 +288,8 @@ export interface UpdateRulesOptions { threatIndex: ThreatIndexOrUndefined; threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; threatLanguage: ThreatLanguageOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: To; @@ -327,6 +333,8 @@ export interface PatchRulesOptions { severityMapping: SeverityMappingOrUndefined; tags: TagsOrUndefined; threat: ThreatOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; threshold: ThresholdOrUndefined; threatFilters: ThreatFiltersOrUndefined; threatIndex: ThreatIndexOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index c685c4198c1193..3d4b27b74c0af0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -52,6 +52,8 @@ export const updatePrepackagedRules = async ( threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, timestamp_override: timestampOverride, references, version, @@ -107,6 +109,8 @@ export const updatePrepackagedRules = async ( threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, references, version, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts index a33651580ef221..34be0f6ad843dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts @@ -49,6 +49,8 @@ export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ threatMapping: undefined, threatLanguage: undefined, timestampOverride: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: 'now', type: 'query', references: ['http://www.example.com'], @@ -99,6 +101,8 @@ export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({ threatMapping: undefined, threatLanguage: undefined, timestampOverride: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: 'now', type: 'machine_learning', references: ['http://www.example.com'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index 3da921ed47f26e..5168affca5c624 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -50,6 +50,8 @@ export const updateRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, @@ -99,6 +101,8 @@ export const updateRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 654383ff97c7ad..8555af424ecd79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -60,6 +60,8 @@ describe('utils', () => { threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: undefined, timestampOverride: undefined, type: undefined, @@ -108,6 +110,8 @@ describe('utils', () => { threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: undefined, timestampOverride: undefined, type: undefined, @@ -158,6 +162,8 @@ describe('utils', () => { threatLanguage: undefined, to: undefined, timestampOverride: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, type: undefined, references: undefined, version: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index a9a100543b528a..83d9e3fd3e59f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -43,6 +43,8 @@ import { } from '../../../../common/detection_engine/schemas/common/schemas'; import { PartialFilter } from '../types'; import { + ConcurrentSearchesOrUndefined, + ItemsPerSearchOrUndefined, ListArrayOrUndefined, ThreatFiltersOrUndefined, ThreatIndexOrUndefined, @@ -98,6 +100,8 @@ export interface UpdateProperties { threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; threatLanguage: ThreatLanguageOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: ToOrUndefined; type: TypeOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh index 23c1914387c44d..4807afd71e8d2c 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh @@ -12,7 +12,7 @@ set -e # Adds port mock data to a threat list for testing. # Example: ./create_threat_data.sh -# Example: ./create_threat_data.sh 1000 2000 +# Example: ./create_threat_data.sh 1 500 START=${1:-1} END=${2:-1000} @@ -22,7 +22,7 @@ do { curl -s -k \ -H "Content-Type: application/json" \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X PUT ${ELASTICSEARCH_URL}/mock-threat-list/_doc/$i \ + -X PUT ${ELASTICSEARCH_URL}/mock-threat-list-1/_doc/$i \ --data " { \"@timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json new file mode 100644 index 00000000000000..c573db7fbca359 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json @@ -0,0 +1,32 @@ +{ + "concurrent_searches": 10, + "items_per_search": 10, + "index": ["auditbeat-*", "endgame-*", "filebeat-*", "logs-*", "packetbeat-*", "winlogbeat-*"], + "name": "Indicator Match Concurrent Searches", + "description": "Does 100 Concurrent searches with 10 items per search", + "rule_id": "indicator_concurrent_search", + "risk_score": 1, + "severity": "high", + "type": "threat_match", + "query": "*:*", + "tags": ["concurrent_searches_test", "from_script"], + "threat_index": ["mock-threat-list-1"], + "threat_language": "kuery", + "threat_query": "*:*", + "threat_mapping": [ + { + "entries": [ + { + "field": "source.port", + "type": "mapping", + "value": "source.port" + }, + { + "field": "source.ip", + "type": "mapping", + "value": "source.ip" + } + ] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 4559a658c9583c..92e6b9562d9706 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -68,6 +68,8 @@ export const sampleRuleAlertParams = ( threat: undefined, version: 1, exceptionsList: getListArrayMock(), + concurrentSearches: undefined, + itemsPerSearch: undefined, }); export const sampleRuleSO = (): SavedObject => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index cfe71f66395b04..50e740e81830f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -54,6 +54,8 @@ const signalSchema = schema.object({ threatQuery: schema.maybe(schema.string()), threatMapping: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threatLanguage: schema.maybe(schema.string()), + concurrentSearches: schema.maybe(schema.number()), + itemsPerSearch: schema.maybe(schema.number()), }); /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 415abc9d995fba..dc68e3949eb363 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -504,7 +504,7 @@ describe('rules_notification_alert_type', () => { await alert.executor(payload); expect(logger.error).toHaveBeenCalled(); expect(logger.error.mock.calls[0][0]).toContain( - 'An error occurred during rule execution: message: "Threat Match rule is missing threatQuery and/or threatIndex and/or threatMapping: threatQuery: "undefined" threatIndex: "undefined" threatMapping: "undefined"" name: "Detect Root/Admin Users" id: "04128c15-0d1b-4716-a4c5-46997ac7f3bd" rule id: "rule-1" signals index: ".siem-signals"' + 'An error occurred during rule execution: message: "Indicator match is missing threatQuery and/or threatIndex and/or threatMapping: threatQuery: "undefined" threatIndex: "undefined" threatMapping: "undefined"" name: "Detect Root/Admin Users" id: "04128c15-0d1b-4716-a4c5-46997ac7f3bd" rule id: "rule-1" signals index: ".siem-signals"' ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index a0d5c833b208cb..1d2b1c23f868f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -119,6 +119,8 @@ export const signalRulesAlertType = ({ timestampOverride, type, exceptionsList, + concurrentSearches, + itemsPerSearch, } = params; const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE); @@ -360,7 +362,7 @@ export const signalRulesAlertType = ({ ) { throw new Error( [ - 'Threat Match rule is missing threatQuery and/or threatIndex and/or threatMapping:', + 'Indicator match is missing threatQuery and/or threatIndex and/or threatMapping:', `threatQuery: "${threatQuery}"`, `threatIndex: "${threatIndex}"`, `threatMapping: "${threatMapping}"`, @@ -403,6 +405,8 @@ export const signalRulesAlertType = ({ threatLanguage, buildRuleMessage, threatIndex, + concurrentSearches: concurrentSearches ?? 1, + itemsPerSearch: itemsPerSearch ?? 9000, }); } else if (type === 'query' || type === 'saved_query') { const inputIndex = await getInputIndex(services, version, index); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts index 85d172b3631a93..8eed838fc9680b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts @@ -19,28 +19,32 @@ import { } from './build_threat_mapping_filter'; import { getThreatMappingMock, - getThreatListSearchResponseMock, getThreatListItemMock, getThreatMappingFilterMock, getFilterThreatMapping, getThreatMappingFiltersShouldMock, getThreatMappingFilterShouldMock, + getThreatListSearchResponseMock, } from './build_threat_mapping_filter.mock'; -import { BooleanFilter } from './types'; +import { BooleanFilter, ThreatListItem } from './types'; describe('build_threat_mapping_filter', () => { describe('buildThreatMappingFilter', () => { test('it should throw if given a chunk over 1024 in size', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; expect(() => - buildThreatMappingFilter({ threatMapping, threatList, chunkSize: 1025 }) + buildThreatMappingFilter({ + threatMapping, + threatList, + chunkSize: 1025, + }) ).toThrow('chunk sizes cannot exceed 1024 in size'); }); test('it should NOT throw if given a chunk under 1024 in size', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; expect(() => buildThreatMappingFilter({ threatMapping, threatList, chunkSize: 1023 }) ).not.toThrow(); @@ -48,30 +52,30 @@ describe('build_threat_mapping_filter', () => { test('it should create the correct entries when using the default mocks', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const filter = buildThreatMappingFilter({ threatMapping, threatList }); expect(filter).toEqual(getThreatMappingFilterMock()); }); test('it should not mutate the original threatMapping', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; buildThreatMappingFilter({ threatMapping, threatList }); expect(threatMapping).toEqual(getThreatMappingMock()); }); test('it should not mutate the original threatListItem', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; buildThreatMappingFilter({ threatMapping, threatList }); - expect(threatList).toEqual(getThreatListSearchResponseMock()); + expect(threatList).toEqual(getThreatListSearchResponseMock().hits.hits); }); }); describe('filterThreatMapping', () => { test('it should not remove any entries when using the default mocks', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const item = filterThreatMapping({ threatMapping, threatListItem }); const expected = getFilterThreatMapping(); @@ -80,7 +84,7 @@ describe('build_threat_mapping_filter', () => { test('it should only give one filtered element if only 1 element is defined', () => { const [firstElement] = getThreatMappingMock(); // get only the first element - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const item = filterThreatMapping({ threatMapping: [firstElement], threatListItem }); const [firstElementFilter] = getFilterThreatMapping(); // get only the first element to compare @@ -89,7 +93,7 @@ describe('build_threat_mapping_filter', () => { test('it should not mutate the original threatMapping', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; filterThreatMapping({ threatMapping, @@ -100,13 +104,13 @@ describe('build_threat_mapping_filter', () => { test('it should not mutate the original threatListItem', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; filterThreatMapping({ threatMapping, threatListItem, }); - expect(threatListItem).toEqual(getThreatListItemMock()); + expect(threatListItem).toEqual(getThreatListSearchResponseMock().hits.hits[0]); }); test('it should remove the entire "AND" clause if one of the pieces of data is missing from the list', () => { @@ -166,9 +170,11 @@ describe('build_threat_mapping_filter', () => { }, ], threatListItem: { - '@timestamp': '2020-09-09T21:59:13Z', - host: { - name: 'host-1', + _source: { + '@timestamp': '2020-09-09T21:59:13Z', + host: { + name: 'host-1', + }, }, }, }); @@ -189,7 +195,7 @@ describe('build_threat_mapping_filter', () => { describe('createInnerAndClauses', () => { test('it should return two clauses given a single entry', () => { const [{ entries: threatMappingEntries }] = getThreatMappingMock(); // get the first element - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); const { bool: { @@ -219,7 +225,7 @@ describe('build_threat_mapping_filter', () => { type: 'mapping', }, ]; - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); const { bool: { @@ -248,7 +254,7 @@ describe('build_threat_mapping_filter', () => { type: 'mapping', }, ]; - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); const { bool: { @@ -275,7 +281,7 @@ describe('build_threat_mapping_filter', () => { type: 'mapping', }, ]; - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); expect(innerClause).toEqual([]); }); @@ -284,27 +290,31 @@ describe('build_threat_mapping_filter', () => { describe('createAndOrClauses', () => { test('it should return all clauses given the entries', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createAndOrClauses({ threatMapping, threatListItem }); expect(innerClause).toEqual(getThreatMappingFilterShouldMock()); }); test('it should filter out data from entries that do not have mappings', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = { ...getThreatListItemMock(), foo: 'bar' }; + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; + threatListItem._source = { + ...getThreatListSearchResponseMock().hits.hits[0]._source, + foo: 'bar', + }; const innerClause = createAndOrClauses({ threatMapping, threatListItem }); expect(innerClause).toEqual(getThreatMappingFilterShouldMock()); }); test('it should return an empty boolean given an empty array', () => { - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createAndOrClauses({ threatMapping: [], threatListItem }); expect(innerClause).toEqual({ bool: { minimum_should_match: 1, should: [] } }); }); test('it should return an empty boolean clause given an empty object for a threat list item', () => { const threatMapping = getThreatMappingMock(); - const innerClause = createAndOrClauses({ threatMapping, threatListItem: {} }); + const innerClause = createAndOrClauses({ threatMapping, threatListItem: { _source: {} } }); expect(innerClause).toEqual({ bool: { minimum_should_match: 1, should: [] } }); }); }); @@ -312,7 +322,7 @@ describe('build_threat_mapping_filter', () => { describe('buildEntriesMappingFilter', () => { test('it should return all clauses given the entries', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const mapping = buildEntriesMappingFilter({ threatMapping, threatList, @@ -326,8 +336,7 @@ describe('build_threat_mapping_filter', () => { test('it should return empty "should" given an empty threat list', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); - threatList.hits.hits = []; + const threatList: ThreatListItem[] = []; const mapping = buildEntriesMappingFilter({ threatMapping, threatList, @@ -340,7 +349,7 @@ describe('build_threat_mapping_filter', () => { }); test('it should return empty "should" given an empty threat mapping', () => { - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const mapping = buildEntriesMappingFilter({ threatMapping: [], threatList, @@ -374,7 +383,7 @@ describe('build_threat_mapping_filter', () => { }, ], ]; - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const mapping = buildEntriesMappingFilter({ threatMapping, threatList, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts index 346f156a9ec338..294d97e0bf2f12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts @@ -53,9 +53,9 @@ export const filterThreatMapping = ({ }: FilterThreatMappingOptions): ThreatMapping => threatMapping .map((threatMap) => { - const atLeastOneItemMissingInThreatList = threatMap.entries.some( - (entry) => get(entry.value, threatListItem) == null - ); + const atLeastOneItemMissingInThreatList = threatMap.entries.some((entry) => { + return get(entry.value, threatListItem._source) == null; + }); if (atLeastOneItemMissingInThreatList) { return { ...threatMap, entries: [] }; } else { @@ -69,7 +69,7 @@ export const createInnerAndClauses = ({ threatListItem, }: CreateInnerAndClausesOptions): BooleanFilter[] => { return threatMappingEntries.reduce((accum, threatMappingEntry) => { - const value = get(threatMappingEntry.value, threatListItem); + const value = get(threatMappingEntry.value, threatListItem._source); if (value != null) { // These values could be potentially 10k+ large so mutating the array intentionally accum.push({ @@ -114,24 +114,21 @@ export const buildEntriesMappingFilter = ({ threatList, chunkSize, }: BuildEntriesMappingFilterOptions): BooleanFilter => { - const combinedShould = threatList.hits.hits.reduce( - (accum, threatListSearchItem) => { - const filteredEntries = filterThreatMapping({ - threatMapping, - threatListItem: threatListSearchItem._source, - }); - const queryWithAndOrClause = createAndOrClauses({ - threatMapping: filteredEntries, - threatListItem: threatListSearchItem._source, - }); - if (queryWithAndOrClause.bool.should.length !== 0) { - // These values can be 10k+ large, so using a push here for performance - accum.push(queryWithAndOrClause); - } - return accum; - }, - [] - ); + const combinedShould = threatList.reduce((accum, threatListSearchItem) => { + const filteredEntries = filterThreatMapping({ + threatMapping, + threatListItem: threatListSearchItem, + }); + const queryWithAndOrClause = createAndOrClauses({ + threatMapping: filteredEntries, + threatListItem: threatListSearchItem, + }); + if (queryWithAndOrClause.bool.should.length !== 0) { + // These values can be 10k+ large, so using a push here for performance + accum.push(queryWithAndOrClause); + } + return accum; + }, []); const should = splitShouldClauses({ should: combinedShould, chunkSize }); return { bool: { should, minimum_should_match: 1 } }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts index 037f91240edfaa..43fb759d076203 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getThreatList } from './get_threat_list'; import { buildThreatMappingFilter } from './build_threat_mapping_filter'; import { getFilter } from '../get_filter'; import { searchAfterAndBulkCreate } from '../search_after_bulk_create'; -import { CreateThreatSignalOptions, ThreatSignalResults } from './types'; -import { combineResults } from './utils'; +import { CreateThreatSignalOptions } from './types'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; export const createThreatSignal = async ({ threatMapping, @@ -41,28 +40,11 @@ export const createThreatSignal = async ({ refresh, tags, throttle, - threatFilters, - threatQuery, - threatLanguage, buildRuleMessage, - threatIndex, name, currentThreatList, currentResult, -}: CreateThreatSignalOptions): Promise => { - const threatList = await getThreatList({ - callCluster: services.callCluster, - exceptionItems, - query: threatQuery, - language: threatLanguage, - threatFilters, - index: threatIndex, - searchAfter: currentThreatList.hits.hits[currentThreatList.hits.hits.length - 1].sort, - sortField: undefined, - sortOrder: undefined, - listClient, - }); - +}: CreateThreatSignalOptions): Promise => { const threatFilter = buildThreatMappingFilter({ threatMapping, threatList: currentThreatList, @@ -71,7 +53,12 @@ export const createThreatSignal = async ({ if (threatFilter.query.bool.should.length === 0) { // empty threat list and we do not want to return everything as being // a hit so opt to return the existing result. - return { threatList, results: currentResult }; + logger.debug( + buildRuleMessage( + 'Indicator items are empty after filtering for missing data, returning without attempting a match' + ) + ); + return currentResult; } else { const esFilter = await getFilter({ type, @@ -83,7 +70,13 @@ export const createThreatSignal = async ({ index: inputIndex, lists: exceptionItems, }); - const newResult = await searchAfterAndBulkCreate({ + + logger.debug( + buildRuleMessage( + `${threatFilter.query.bool.should.length} indicator items are being checked for existence of matches` + ) + ); + const result = await searchAfterAndBulkCreate({ gap, previousStartedAt, listClient, @@ -110,7 +103,15 @@ export const createThreatSignal = async ({ throttle, buildRuleMessage, }); - const results = combineResults(currentResult, newResult); - return { threatList, results }; + logger.debug( + buildRuleMessage( + `${ + threatFilter.query.bool.should.length + } items have completed match checks and the total times to search were ${ + result.searchAfterTimes.length !== 0 ? result.searchAfterTimes : '(unknown) ' + }ms` + ) + ); + return result; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index 8be76dc8caf0f9..e90c45d40de950 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getThreatList } from './get_threat_list'; +import chunk from 'lodash/fp/chunk'; +import { getThreatList, getThreatListCount } from './get_threat_list'; import { CreateThreatSignalsOptions } from './types'; import { createThreatSignal } from './create_threat_signal'; import { SearchAfterAndBulkCreateReturnType } from '../types'; +import { combineConcurrentResults } from './utils'; export const createThreatSignals = async ({ threatMapping, @@ -45,7 +47,12 @@ export const createThreatSignals = async ({ buildRuleMessage, threatIndex, name, + concurrentSearches, + itemsPerSearch, }: CreateThreatSignalsOptions): Promise => { + logger.debug(buildRuleMessage('Indicator matching rule starting')); + const perPage = concurrentSearches * itemsPerSearch; + let results: SearchAfterAndBulkCreateReturnType = { success: true, bulkCreateTimes: [], @@ -55,6 +62,16 @@ export const createThreatSignals = async ({ errors: [], }; + let threatListCount = await getThreatListCount({ + callCluster: services.callCluster, + exceptionItems, + threatFilters, + query: threatQuery, + language: threatLanguage, + index: threatIndex, + }); + logger.debug(buildRuleMessage(`Total indicator items: ${threatListCount}`)); + let threatList = await getThreatList({ callCluster: services.callCluster, exceptionItems, @@ -66,47 +83,89 @@ export const createThreatSignals = async ({ searchAfter: undefined, sortField: undefined, sortOrder: undefined, + logger, + buildRuleMessage, + perPage, }); - while (threatList.hits.hits.length !== 0 && results.createdSignalsCount <= params.maxSignals) { - ({ threatList, results } = await createThreatSignal({ - threatMapping, - query, - inputIndex, - type, - filters, - language, - savedId, - services, + while (threatList.hits.hits.length !== 0) { + const chunks = chunk(itemsPerSearch, threatList.hits.hits); + logger.debug(buildRuleMessage(`${chunks.length} concurrent indicator searches are starting.`)); + const concurrentSearchesPerformed = chunks.map>( + (slicedChunk) => + createThreatSignal({ + threatMapping, + query, + inputIndex, + type, + filters, + language, + savedId, + services, + exceptionItems, + gap, + previousStartedAt, + listClient, + logger, + eventsTelemetry, + alertId, + outputIndex, + params, + searchAfterSize, + actions, + createdBy, + createdAt, + updatedBy, + updatedAt, + interval, + enabled, + tags, + refresh, + throttle, + buildRuleMessage, + name, + currentThreatList: slicedChunk, + currentResult: results, + }) + ); + const searchesPerformed = await Promise.all(concurrentSearchesPerformed); + results = combineConcurrentResults(results, searchesPerformed); + threatListCount -= threatList.hits.hits.length; + logger.debug( + buildRuleMessage( + `Concurrent indicator match searches completed with ${results.createdSignalsCount} signals found`, + `search times of ${results.searchAfterTimes}ms,`, + `bulk create times ${results.bulkCreateTimes}ms,`, + `all successes are ${results.success}` + ) + ); + if (results.createdSignalsCount >= params.maxSignals) { + logger.debug( + buildRuleMessage( + `Indicator match has reached its max signals count ${params.maxSignals}. Additional indicator items not checked are ${threatListCount}` + ) + ); + break; + } + logger.debug(buildRuleMessage(`Indicator items left to check are ${threatListCount}`)); + + threatList = await getThreatList({ + callCluster: services.callCluster, exceptionItems, - gap, - previousStartedAt, - listClient, - logger, - eventsTelemetry, - alertId, - outputIndex, - params, - searchAfterSize, - actions, - createdBy, - createdAt, - updatedBy, - updatedAt, - interval, - enabled, - tags, - refresh, - throttle, + query: threatQuery, + language: threatLanguage, threatFilters, - threatQuery, + index: threatIndex, + searchAfter: threatList.hits.hits[threatList.hits.hits.length - 1].sort, + sortField: undefined, + sortOrder: undefined, + listClient, buildRuleMessage, - threatIndex, - threatLanguage, - name, - currentThreatList: threatList, - currentResult: results, - })); + logger, + perPage, + }); } + + logger.debug(buildRuleMessage('Indicator matching rule has completed')); return results; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts index 3147eb1705168a..aba3f6f69d706f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts @@ -10,6 +10,7 @@ import { GetSortWithTieBreakerOptions, GetThreatListOptions, SortWithTieBreaker, + ThreatListCountOptions, ThreatListItem, } from './types'; @@ -30,6 +31,8 @@ export const getThreatList = async ({ exceptionItems, threatFilters, listClient, + buildRuleMessage, + logger, }: GetThreatListOptions): Promise> => { const calculatedPerPage = perPage ?? MAX_PER_PAGE; if (calculatedPerPage > 10000) { @@ -43,6 +46,11 @@ export const getThreatList = async ({ exceptionItems ); + logger.debug( + buildRuleMessage( + `Querying the indicator items from the index: "${index}" with searchAfter: "${searchAfter}" for up to ${calculatedPerPage} indicator items` + ) + ); const response: SearchResponse = await callCluster('search', { body: { query: queryFilter, @@ -58,6 +66,8 @@ export const getThreatList = async ({ index, size: calculatedPerPage, }); + + logger.debug(buildRuleMessage(`Retrieved indicator items of size: ${response.hits.hits.length}`)); return response; }; @@ -89,3 +99,30 @@ export const getSortWithTieBreaker = ({ } } }; + +export const getThreatListCount = async ({ + callCluster, + query, + language, + threatFilters, + index, + exceptionItems, +}: ThreatListCountOptions): Promise => { + const queryFilter = getQueryFilter( + query, + language ?? 'kuery', + threatFilters, + index, + exceptionItems + ); + const response: { + count: number; + } = await callCluster('count', { + body: { + query: queryFilter, + }, + ignoreUnavailable: true, + index, + }); + return response.count; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index 0078cf1b3c64f8..2e32a4e682403f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -5,7 +5,6 @@ */ import { Duration } from 'moment'; -import { SearchResponse } from 'elasticsearch'; import { ListClient } from '../../../../../../lists/server'; import { Type, @@ -17,6 +16,8 @@ import { ThreatMappingEntries, ThreatIndex, ThreatLanguageOrUndefined, + ConcurrentSearches, + ItemsPerSearch, } from '../../../../../common/detection_engine/schemas/types/threat_mapping'; import { PartialFilter, RuleTypeParams } from '../../types'; import { AlertServices } from '../../../../../../alerts/server'; @@ -62,6 +63,8 @@ export interface CreateThreatSignalsOptions { threatIndex: ThreatIndex; threatLanguage: ThreatLanguageOrUndefined; name: string; + concurrentSearches: ConcurrentSearches; + itemsPerSearch: ItemsPerSearch; } export interface CreateThreatSignalOptions { @@ -93,24 +96,15 @@ export interface CreateThreatSignalOptions { tags: string[]; refresh: false | 'wait_for'; throttle: string; - threatFilters: PartialFilter[]; - threatQuery: ThreatQuery; buildRuleMessage: BuildRuleMessage; - threatIndex: ThreatIndex; - threatLanguage: ThreatLanguageOrUndefined; name: string; - currentThreatList: SearchResponse; + currentThreatList: ThreatListItem[]; currentResult: SearchAfterAndBulkCreateReturnType; } -export interface ThreatSignalResults { - threatList: SearchResponse; - results: SearchAfterAndBulkCreateReturnType; -} - export interface BuildThreatMappingFilterOptions { threatMapping: ThreatMapping; - threatList: SearchResponse; + threatList: ThreatListItem[]; chunkSize?: number; } @@ -131,7 +125,7 @@ export interface CreateAndOrClausesOptions { export interface BuildEntriesMappingFilterOptions { threatMapping: ThreatMapping; - threatList: SearchResponse; + threatList: ThreatListItem[]; chunkSize: number; } @@ -156,6 +150,17 @@ export interface GetThreatListOptions { threatFilters: PartialFilter[]; exceptionItems: ExceptionListItemSchema[]; listClient: ListClient; + buildRuleMessage: BuildRuleMessage; + logger: Logger; +} + +export interface ThreatListCountOptions { + callCluster: ILegacyScopedClusterClient['callAsCurrentUser']; + query: string; + language: ThreatLanguageOrUndefined; + threatFilters: PartialFilter[]; + index: string[]; + exceptionItems: ExceptionListItemSchema[]; } export interface GetSortWithTieBreakerOptions { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index 27593b40b0c8f6..840d64381c7932 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -6,7 +6,13 @@ import { SearchAfterAndBulkCreateReturnType } from '../types'; -import { calculateAdditiveMax, combineResults } from './utils'; +import { + calculateAdditiveMax, + calculateMax, + calculateMaxLookBack, + combineConcurrentResults, + combineResults, +} from './utils'; describe('utils', () => { describe('calculateAdditiveMax', () => { @@ -156,4 +162,383 @@ describe('utils', () => { ); }); }); + + describe('calculateMax', () => { + test('it should return 0 for two empty arrays', () => { + const max = calculateMax([], []); + expect(max).toEqual('0'); + }); + + test('it should return 5 for two arrays with the numbers 5', () => { + const max = calculateMax(['5'], ['5']); + expect(max).toEqual('5'); + }); + + test('it should return 5 for two arrays with second array having just 5', () => { + const max = calculateMax([], ['5']); + expect(max).toEqual('5'); + }); + + test('it should return 5 for two arrays with first array having just 5', () => { + const max = calculateMax(['5'], []); + expect(max).toEqual('5'); + }); + + test('it should return 10 for the max of the two arrays when the max of each array is 10', () => { + const max = calculateMax(['3', '5', '1'], ['3', '5', '10']); + expect(max).toEqual('10'); + }); + + test('it should return 10 for the max of the two arrays when the max of the first is 10', () => { + const max = calculateMax(['3', '5', '10'], ['3', '5', '1']); + expect(max).toEqual('10'); + }); + }); + + describe('calculateMaxLookBack', () => { + test('it should return null if both are null', () => { + const max = calculateMaxLookBack(null, null); + expect(max).toEqual(null); + }); + + test('it should return undefined if both are undefined', () => { + const max = calculateMaxLookBack(undefined, undefined); + expect(max).toEqual(undefined); + }); + + test('it should return null if both one is null and other other is undefined', () => { + const max = calculateMaxLookBack(undefined, null); + expect(max).toEqual(null); + }); + + test('it should return null if both one is null and other other is undefined with flipped arguments', () => { + const max = calculateMaxLookBack(null, undefined); + expect(max).toEqual(null); + }); + + test('it should return a date time if one argument is null', () => { + const max = calculateMaxLookBack(null, new Date('2020-09-16T03:34:32.390Z')); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time if one argument is null with flipped arguments', () => { + const max = calculateMaxLookBack(new Date('2020-09-16T03:34:32.390Z'), null); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time if one argument is undefined', () => { + const max = calculateMaxLookBack(new Date('2020-09-16T03:34:32.390Z'), undefined); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time if one argument is undefined with flipped arguments', () => { + const max = calculateMaxLookBack(undefined, new Date('2020-09-16T03:34:32.390Z')); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time that is larger than the other', () => { + const max = calculateMaxLookBack( + new Date('2020-10-16T03:34:32.390Z'), + new Date('2020-09-16T03:34:32.390Z') + ); + expect(max).toEqual(new Date('2020-10-16T03:34:32.390Z')); + }); + + test('it should return a date time that is larger than the other with arguments flipped', () => { + const max = calculateMaxLookBack( + new Date('2020-09-16T03:34:32.390Z'), + new Date('2020-10-16T03:34:32.390Z') + ); + expect(max).toEqual(new Date('2020-10-16T03:34:32.390Z')); + }); + }); + + describe('combineConcurrentResults', () => { + test('it should use the maximum found if given an empty array for newResults', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['30'], // max value from existingResult.searchAfterTimes + bulkCreateTimes: ['25'], // max value from existingResult.bulkCreateTimes + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, []); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should work with empty arrays for searchAfterTimes and bulkCreateTimes', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: undefined, + createdSignalsCount: 0, + errors: [], + }; + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['30'], // max value from existingResult.searchAfterTimes + bulkCreateTimes: ['25'], // max value from existingResult.bulkCreateTimes + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should get the max of two new results and then combine the result with an existingResult correctly', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], // max is 30 + bulkCreateTimes: ['5', '15', '25'], // max is 25 + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult1: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 5, + errors: [], + }; + const newResult2: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['40', '5', '15'], + bulkCreateTimes: ['50', '5', '15'], + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), + createdSignalsCount: 8, + errors: [], + }; + + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) + bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), // max lastLookBackDate + createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult1, newResult2]); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should get the max of two new results and then combine the result with an existingResult correctly when the results are flipped around', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], // max is 30 + bulkCreateTimes: ['5', '15', '25'], // max is 25 + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult1: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 5, + errors: [], + }; + const newResult2: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['40', '5', '15'], + bulkCreateTimes: ['50', '5', '15'], + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), + createdSignalsCount: 8, + errors: [], + }; + + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) + bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), // max lastLookBackDate + createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult2, newResult1]); // two array elements are flipped + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should return the max date correctly if one date contains a null', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], // max is 30 + bulkCreateTimes: ['5', '15', '25'], // max is 25 + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult1: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 5, + errors: [], + }; + const newResult2: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['40', '5', '15'], + bulkCreateTimes: ['50', '5', '15'], + lastLookBackDate: null, + createdSignalsCount: 8, + errors: [], + }; + + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) + bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), // max lastLookBackDate + createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult1, newResult2]); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should combine two results with success set to "true" if both are "true"', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults.success).toEqual(true); + }); + + test('it should combine two results with success set to "false" if one of them is "false"', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults.success).toEqual(false); + }); + + test('it should use the latest date if it is set in the new result', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults.lastLookBackDate?.toISOString()).toEqual('2020-09-16T03:34:32.390Z'); + }); + + test('it should combine the searchAfterTimes and the bulkCreateTimes', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults).toEqual( + expect.objectContaining({ + searchAfterTimes: ['60'], + bulkCreateTimes: ['50'], + }) + ); + }); + + test('it should combine errors together without duplicates', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: ['error 1', 'error 2', 'error 3'], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 3, + errors: ['error 4', 'error 1', 'error 3', 'error 5'], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults).toEqual( + expect.objectContaining({ + errors: ['error 1', 'error 2', 'error 3', 'error 4', 'error 5'], + }) + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts index 401a4a1acb0652..d6c91fad6d9cb0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts @@ -19,6 +19,41 @@ export const calculateAdditiveMax = (existingTimers: string[], newTimers: string return [String(numericNewTimerMax + numericExistingTimerMax)]; }; +/** + * Given two timers this will take the max of each and then get the max from each. + * Max(Max(timer_array_1), Max(timer_array_2)) + * @param existingTimers String array of existing timers + * @param newTimers String array of new timers. + * @returns String array of the new maximum between the two timers + */ +export const calculateMax = (existingTimers: string[], newTimers: string[]): string => { + const numericNewTimerMax = Math.max(0, ...newTimers.map((time) => +time)); + const numericExistingTimerMax = Math.max(0, ...existingTimers.map((time) => +time)); + return String(Math.max(numericNewTimerMax, numericExistingTimerMax)); +}; + +/** + * Given two dates this will return the larger of the two unless one of them is null + * or undefined. If both one or the other is null/undefined it will return the newDate. + * If there is a mix of "undefined" and "null", this will prefer to set it to "null" as having + * a higher value than "undefined" + * @param existingDate The existing date which can be undefined or null or a date + * @param newDate The new date which can be undefined or null or a date + */ +export const calculateMaxLookBack = ( + existingDate: Date | null | undefined, + newDate: Date | null | undefined +): Date | null | undefined => { + const newDateValue = newDate === null ? 1 : newDate === undefined ? 0 : newDate.valueOf(); + const existingDateValue = + existingDate === null ? 1 : existingDate === undefined ? 0 : existingDate.valueOf(); + if (newDateValue >= existingDateValue) { + return newDate; + } else { + return existingDate; + } +}; + /** * Combines two results together and returns the results combined * @param currentResult The current result to combine with a newResult @@ -38,3 +73,39 @@ export const combineResults = ( createdSignalsCount: currentResult.createdSignalsCount + newResult.createdSignalsCount, errors: [...new Set([...currentResult.errors, ...newResult.errors])], }); + +/** + * Combines two results together and returns the results combined + * @param currentResult The current result to combine with a newResult + * @param newResult The new result to combine + */ +export const combineConcurrentResults = ( + currentResult: SearchAfterAndBulkCreateReturnType, + newResult: SearchAfterAndBulkCreateReturnType[] +): SearchAfterAndBulkCreateReturnType => { + const maxedNewResult = newResult.reduce( + (accum, item) => { + const maxSearchAfterTime = calculateMax(accum.searchAfterTimes, item.searchAfterTimes); + const maxBulkCreateTimes = calculateMax(accum.bulkCreateTimes, item.bulkCreateTimes); + const lastLookBackDate = calculateMaxLookBack(accum.lastLookBackDate, item.lastLookBackDate); + return { + success: accum.success && item.success, + searchAfterTimes: [maxSearchAfterTime], + bulkCreateTimes: [maxBulkCreateTimes], + lastLookBackDate, + createdSignalsCount: accum.createdSignalsCount + item.createdSignalsCount, + errors: [...new Set([...accum.errors, ...item.errors])], + }; + }, + { + success: true, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: undefined, + createdSignalsCount: 0, + errors: [], + } + ); + + return combineResults(currentResult, maxedNewResult); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index cf4d989c1f4c84..5cac76e2b0c014 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -44,6 +44,8 @@ import { ThreatQueryOrUndefined, ThreatMappingOrUndefined, ThreatLanguageOrUndefined, + ConcurrentSearchesOrUndefined, + ItemsPerSearchOrUndefined, } from '../../../common/detection_engine/schemas/types/threat_mapping'; import { LegacyCallAPIOptions } from '../../../../../../src/core/server'; @@ -93,6 +95,8 @@ export interface RuleTypeParams { references: References; version: Version; exceptionsList: ListArrayOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; } // eslint-disable-next-line @typescript-eslint/no-explicit-any From 215a561ab76373acc064879e056f62c9d7d4beca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Sat, 7 Nov 2020 11:48:19 +0100 Subject: [PATCH 48/81] [Security Solution] Fix EventsViewer DnD cypress tests (#82619) --- .../cypress/integration/events_viewer.spec.ts | 6 +++--- .../cypress/screens/hosts/events.ts | 3 +++ .../security_solution/cypress/tasks/common.ts | 16 +++++++++++----- .../cypress/tasks/hosts/events.ts | 6 +++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index 13fa9592469e4b..9eb49c19c23f6c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -142,10 +142,11 @@ describe('Events Viewer', () => { }); }); - context.skip('Events columns', () => { + context('Events columns', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); + cy.scrollTo('bottom'); waitsForEventsToBeLoaded(); }); @@ -160,9 +161,8 @@ describe('Events Viewer', () => { const expectedOrderAfterDragAndDrop = 'message@timestamphost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; - cy.scrollTo('bottom'); cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); - dragAndDropColumn({ column: 0, newPosition: 1 }); + dragAndDropColumn({ column: 0, newPosition: 0 }); cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts index 0434de7bff88ea..cf507924a753fe 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts @@ -34,3 +34,6 @@ export const LOAD_MORE = '[data-test-subj="events-viewer-panel"] [data-test-subj="TimelineMoreButton"'; export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; + +export const EVENTS_VIEWER_PAGINATION = + '[data-test-subj="events-viewer-panel"] [data-test-subj="timeline-pagination"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index e16db545999812..bb009f34b02d62 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -23,14 +23,14 @@ export const drag = (subject: JQuery) => { clientY: subjectLocation.top, force: true, }) - .wait(3000) + .wait(300) .trigger('mousemove', { button: primaryButton, clientX: subjectLocation.left + dndSloppyClickDetectionThreshold, clientY: subjectLocation.top, force: true, }) - .wait(3000); + .wait(300); }; /** Drags the subject being dragged on the specified drop target, but does not drop it */ @@ -42,11 +42,17 @@ export const dragWithoutDrop = (dropTarget: JQuery) => { /** "Drops" the subject being dragged on the specified drop target */ export const drop = (dropTarget: JQuery) => { + const targetLocation = dropTarget[0].getBoundingClientRect(); cy.wrap(dropTarget) - .trigger('mousemove', { button: primaryButton, force: true }) - .wait(3000) + .trigger('mousemove', { + button: primaryButton, + clientX: targetLocation.left, + clientY: targetLocation.top, + force: true, + }) + .wait(300) .trigger('mouseup', { force: true }) - .wait(3000); + .wait(300); }; export const reload = (afterReload: () => void) => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts index 226178cd92f18c..401a78767ac578 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -8,6 +8,7 @@ import { drag, drop } from '../common'; import { CLOSE_MODAL, EVENTS_VIEWER_FIELDS_BUTTON, + EVENTS_VIEWER_PAGINATION, FIELDS_BROWSER_CONTAINER, HOST_GEO_CITY_NAME_CHECKBOX, HOST_GEO_COUNTRY_NAME_CHECKBOX, @@ -16,6 +17,7 @@ import { SERVER_SIDE_EVENT_COUNT, } from '../../screens/hosts/events'; import { DRAGGABLE_HEADER } from '../../screens/timeline'; +import { REFRESH_BUTTON } from '../../screens/security_header'; export const addsHostGeoCityNameToHeader = () => { cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({ @@ -53,7 +55,9 @@ export const opensInspectQueryModal = () => { }; export const waitsForEventsToBeLoaded = () => { - cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0'); + cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', '0'); + cy.get(REFRESH_BUTTON).should('not.have.text', 'Updating'); + cy.get(EVENTS_VIEWER_PAGINATION).should('exist'); }; export const dragAndDropColumn = ({ From b8e2e85578f31296efac58580b1d8a306b8874e8 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Sat, 7 Nov 2020 06:46:49 -0700 Subject: [PATCH 49/81] [Maps] rename connected_components/map folder to mb_map (#82897) --- .../layers/vector_tile_layer/vector_tile_layer.js | 2 +- .../maps/public/connected_components/_index.scss | 2 +- .../map_container/map_container.tsx | 2 +- .../{map/mb => mb_map}/draw_control/draw_circle.ts | 0 .../mb => mb_map}/draw_control/draw_control.js | 4 ++-- .../mb => mb_map}/draw_control/draw_tooltip.js | 2 +- .../{map/mb => mb_map}/draw_control/index.js | 4 ++-- .../__snapshots__/feature_properties.test.js.snap | 0 .../__snapshots__/tooltip_header.test.js.snap | 0 .../{map => mb_map}/features_tooltip/_index.scss | 0 .../feature_geometry_filter_form.js | 0 .../features_tooltip/feature_properties.js | 0 .../features_tooltip/feature_properties.test.js | 0 .../features_tooltip/features_tooltip.js | 0 .../features_tooltip/tooltip_header.js | 0 .../features_tooltip/tooltip_header.test.js | 0 .../{map/mb => mb_map}/get_initial_view.ts | 6 +++--- .../{map/mb => mb_map}/image_utils.js | 0 .../{map/mb => mb_map}/index.js | 14 ++++++-------- .../{map/mb => mb_map}/mb.utils.test.js | 2 +- .../{map/mb/view.js => mb_map/mb_map.js} | 10 +++++----- .../{map/mb => mb_map}/sort_layers.test.ts | 4 ++-- .../{map/mb => mb_map}/sort_layers.ts | 2 +- .../__snapshots__/tooltip_control.test.js.snap | 0 .../__snapshots__/tooltip_popover.test.js.snap | 0 .../{map/mb => mb_map}/tooltip_control/index.js | 4 ++-- .../tooltip_control/tooltip_control.js | 4 ++-- .../tooltip_control/tooltip_control.test.js | 0 .../tooltip_control/tooltip_popover.js | 4 ++-- .../tooltip_control/tooltip_popover.test.js | 2 +- .../{map/mb => mb_map}/utils.js | 0 31 files changed, 33 insertions(+), 35 deletions(-) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/draw_control/draw_circle.ts (100%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/draw_control/draw_control.js (97%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/draw_control/draw_tooltip.js (97%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/draw_control/index.js (83%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/__snapshots__/feature_properties.test.js.snap (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/__snapshots__/tooltip_header.test.js.snap (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/_index.scss (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/feature_geometry_filter_form.js (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/feature_properties.js (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/feature_properties.test.js (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/features_tooltip.js (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/tooltip_header.js (100%) rename x-pack/plugins/maps/public/connected_components/{map => mb_map}/features_tooltip/tooltip_header.test.js (100%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/get_initial_view.ts (87%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/image_utils.js (100%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/index.js (84%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/mb.utils.test.js (98%) rename x-pack/plugins/maps/public/connected_components/{map/mb/view.js => mb_map/mb_map.js} (97%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/sort_layers.test.ts (98%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/sort_layers.ts (98%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/__snapshots__/tooltip_control.test.js.snap (100%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/__snapshots__/tooltip_popover.test.js.snap (100%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/index.js (94%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/tooltip_control.js (98%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/tooltip_control.test.js (100%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/tooltip_popover.js (97%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/tooltip_control/tooltip_popover.test.js (98%) rename x-pack/plugins/maps/public/connected_components/{map/mb => mb_map}/utils.js (100%) diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js index 96dad0c01139e4..dc3ace69e5a615 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js @@ -11,7 +11,7 @@ import { isRetina } from '../../../meta'; import { addSpriteSheetToMapFromImageData, loadSpriteSheetImageData, -} from '../../../connected_components/map/mb/utils'; //todo move this implementation +} from '../../../connected_components/mb_map/utils'; //todo move this implementation const MB_STYLE_TYPE_TO_OPACITY = { fill: ['fill-opacity'], diff --git a/x-pack/plugins/maps/public/connected_components/_index.scss b/x-pack/plugins/maps/public/connected_components/_index.scss index a952b3b5459227..19c11d3fde6624 100644 --- a/x-pack/plugins/maps/public/connected_components/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/_index.scss @@ -2,4 +2,4 @@ @import 'layer_panel/index'; @import 'widget_overlay/index'; @import 'toolbar_overlay/index'; -@import 'map/features_tooltip/index'; +@import 'mb_map/features_tooltip/index'; diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 352aed4a8cc935..169875e63a5361 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -13,7 +13,7 @@ import uuid from 'uuid/v4'; import { Filter } from 'src/plugins/data/public'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; // @ts-expect-error -import { MBMap } from '../map/mb'; +import { MBMap } from '../mb_map'; // @ts-expect-error import { WidgetOverlay } from '../widget_overlay'; // @ts-expect-error diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_circle.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_circle.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js index 0356a8267c18af..089d4be28dff7e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js @@ -6,7 +6,7 @@ import _ from 'lodash'; import React from 'react'; -import { DRAW_TYPE } from '../../../../../common/constants'; +import { DRAW_TYPE } from '../../../../common/constants'; import MapboxDraw from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw-unminified'; import DrawRectangle from 'mapbox-gl-draw-rectangle-mode'; import { DrawCircle } from './draw_circle'; @@ -15,7 +15,7 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../../common/elasticsearch_util'; +} from '../../../../common/elasticsearch_util'; import { DrawTooltip } from './draw_tooltip'; const DRAW_RECTANGLE = 'draw_rectangle'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js index c8bde29b94fb68..dd93b038ff8a11 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js @@ -8,7 +8,7 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DRAW_TYPE } from '../../../../../common/constants'; +import { DRAW_TYPE } from '../../../../common/constants'; const noop = () => {}; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js similarity index 83% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js index bc026c41fcf0a4..230ad5b3f39d59 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js @@ -6,8 +6,8 @@ import { connect } from 'react-redux'; import { DrawControl } from './draw_control'; -import { updateDrawState } from '../../../../actions'; -import { getDrawState, isDrawingFilter } from '../../../../selectors/map_selectors'; +import { updateDrawState } from '../../../actions'; +import { getDrawState, isDrawingFilter } from '../../../selectors/map_selectors'; function mapStateToProps(state = {}) { return { diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/feature_properties.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/feature_properties.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/tooltip_header.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/tooltip_header.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/_index.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/_index.scss diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.test.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.test.js diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts b/x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts similarity index 87% rename from x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts index 20fb8186f9870f..853819eb289a3e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { INITIAL_LOCATION } from '../../../../common/constants'; -import { Goto, MapCenterAndZoom } from '../../../../common/descriptor_types'; -import { MapSettings } from '../../../reducers/map'; +import { INITIAL_LOCATION } from '../../../common/constants'; +import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types'; +import { MapSettings } from '../../reducers/map'; export async function getInitialView( goto: Goto | null, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/image_utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/image_utils.js rename to x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/index.js similarity index 84% rename from x-pack/plugins/maps/public/connected_components/map/mb/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/index.js index 4b8df07bd1f391..cccd5e571d3e84 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/index.js @@ -5,7 +5,7 @@ */ import { connect } from 'react-redux'; -import { MBMap } from './view'; +import { MBMap } from './mb_map'; import { mapExtentChanged, mapReady, @@ -14,7 +14,7 @@ import { clearMouseCoordinates, clearGoto, setMapInitError, -} from '../../../actions'; +} from '../../actions'; import { getLayerList, getMapReady, @@ -25,9 +25,9 @@ import { isViewControlHidden, getSpatialFiltersLayer, getMapSettings, -} from '../../../selectors/map_selectors'; +} from '../../selectors/map_selectors'; -import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; +import { getInspectorAdapters } from '../../reducers/non_serializable_instances'; function mapStateToProps(state = {}) { return { @@ -72,7 +72,5 @@ function mapDispatchToProps(dispatch) { }; } -const connectedMBMap = connect(mapStateToProps, mapDispatchToProps, null, { - forwardRef: true, -})(MBMap); -export { connectedMBMap as MBMap }; +const connected = connect(mapStateToProps, mapDispatchToProps)(MBMap); +export { connected as MBMap }; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js index e2050724ef6845..a28cc75f6d89d1 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js @@ -5,7 +5,7 @@ */ import { removeOrphanedSourcesAndLayers } from './utils'; -import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants'; +import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants'; import _ from 'lodash'; class MockMbMap { diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/view.js rename to x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js index ddc48cfc9c329b..04c376a093623b 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js @@ -6,15 +6,15 @@ import _ from 'lodash'; import React from 'react'; -import { ResizeChecker } from '../../../../../../../src/plugins/kibana_utils/public'; +import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils'; import { syncLayerOrder } from './sort_layers'; -import { getGlyphUrl, isRetina } from '../../../meta'; +import { getGlyphUrl, isRetina } from '../../meta'; import { DECIMAL_DEGREES_PRECISION, KBN_TOO_MANY_FEATURES_IMAGE_ID, ZOOM_PRECISION, -} from '../../../../common/constants'; +} from '../../../common/constants'; import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; @@ -23,9 +23,9 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_util'; +import { clampToLatBounds, clampToLonBounds } from '../../../common/elasticsearch_util'; import { getInitialView } from './get_initial_view'; -import { getPreserveDrawingBuffer } from '../../../kibana_services'; +import { getPreserveDrawingBuffer } from '../../kibana_services'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts index e26a1e43509c81..9e85c7b04b2662 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts @@ -8,8 +8,8 @@ import _ from 'lodash'; import { Map as MbMap, Layer as MbLayer, Style as MbStyle } from 'mapbox-gl'; import { getIsTextLayer, syncLayerOrder } from './sort_layers'; -import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants'; -import { ILayer } from '../../../classes/layers/layer'; +import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants'; +import { ILayer } from '../../classes/layers/layer'; let moveCounter = 0; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts index 0c970fe6635576..dda43269e32d83 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts @@ -5,7 +5,7 @@ */ import { Map as MbMap, Layer as MbLayer } from 'mapbox-gl'; -import { ILayer } from '../../../classes/layers/layer'; +import { ILayer } from '../../classes/layers/layer'; // "Layer" is overloaded and can mean the following // 1) Map layer (ILayer): A single map layer consists of one to many mapbox layers. diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_control.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_control.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_control.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_control.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_popover.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_popover.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_popover.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_popover.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js similarity index 94% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js index 407dcf1997aeb2..7d2f2b05d6f11d 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js @@ -11,13 +11,13 @@ import { openOnClickTooltip, closeOnHoverTooltip, openOnHoverTooltip, -} from '../../../../actions'; +} from '../../../actions'; import { getLayerList, getOpenTooltips, getHasLockedTooltips, isDrawingFilter, -} from '../../../../selectors/map_selectors'; +} from '../../../selectors/map_selectors'; function mapStateToProps(state = {}) { return { diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js index edfeb3c76b1046..b178eef6fa5d39 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js @@ -6,9 +6,9 @@ import _ from 'lodash'; import React from 'react'; -import { FEATURE_ID_PROPERTY_NAME, LON_INDEX } from '../../../../../common/constants'; +import { FEATURE_ID_PROPERTY_NAME, LON_INDEX } from '../../../../common/constants'; import { TooltipPopover } from './tooltip_popover'; -import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../../classes/util/mb_filter_expressions'; +import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../classes/util/mb_filter_expressions'; function justifyAnchorLocation(mbLngLat, targetFeature) { let popupAnchorLocation = [mbLngLat.lng, mbLngLat.lat]; // default popup location to mouse location diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.js diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js index 4cfddf00340392..ca4864f79940ee 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js @@ -5,8 +5,8 @@ */ import React, { Component } from 'react'; -import { LAT_INDEX, LON_INDEX } from '../../../../../common/constants'; -import { FeaturesTooltip } from '../../features_tooltip/features_tooltip'; +import { LAT_INDEX, LON_INDEX } from '../../../../common/constants'; +import { FeaturesTooltip } from '../features_tooltip/features_tooltip'; import { EuiPopover, EuiText } from '@elastic/eui'; const noop = () => {}; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js index 205ca7337277d5..b15c3fce6c0b7e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../features_tooltip/features_tooltip', () => ({ +jest.mock('../features_tooltip/features_tooltip', () => ({ FeaturesTooltip: () => { return
mockFeaturesTooltip
; }, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/utils.js rename to x-pack/plugins/maps/public/connected_components/mb_map/utils.js From 387593d7231bc6db7e9b7ba1c8063985b132ce22 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Sat, 7 Nov 2020 19:15:37 -0600 Subject: [PATCH 50/81] Copy `dateAsStringRt` to observability plugin (#82839) Observability was importing `dateAsStringRt` from APM, which creates an implicit circular dependency between the two plugins. Copy that function into where it was being used in observability to remove the dependency. Related to #80508. --- .../observability/common/annotations.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/common/annotations.ts b/x-pack/plugins/observability/common/annotations.ts index 6aea4d3d92f9b4..f7ab243cf73f3c 100644 --- a/x-pack/plugins/observability/common/annotations.ts +++ b/x-pack/plugins/observability/common/annotations.ts @@ -5,7 +5,24 @@ */ import * as t from 'io-ts'; -import { dateAsStringRt } from '../../apm/common/runtime_types/date_as_string_rt'; +import { either } from 'fp-ts/lib/Either'; + +/** + * Checks whether a string is a valid ISO timestamp, + * but doesn't convert it into a Date object when decoding. + * + * Copied from x-pack/plugins/apm/common/runtime_types/date_as_string_rt.ts. + */ +const dateAsStringRt = new t.Type( + 'DateAsString', + t.string.is, + (input, context) => + either.chain(t.string.validate(input, context), (str) => { + const date = new Date(str); + return isNaN(date.getTime()) ? t.failure(input, context) : t.success(str); + }), + t.identity +); export const createAnnotationRt = t.intersection([ t.type({ From 167a4b6665341945e725a10452ea937f59a6a90c Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Sun, 8 Nov 2020 15:51:42 -0500 Subject: [PATCH 51/81] skip flaky suite (#75794) --- .../cypress/integration/timeline_local_storage.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index 383ebe22205859..d518f9e42f21f5 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -13,7 +13,8 @@ import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events'; import { removeColumn, resetFields } from '../tasks/timeline'; -describe('persistent timeline', () => { +// Failing: See https://github.com/elastic/kibana/issues/75794 +describe.skip('persistent timeline', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); From c77de1561b7c58cc90ca1a85e4e966a9487cb600 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Sun, 8 Nov 2020 15:54:22 -0500 Subject: [PATCH 52/81] skip flaky suite (#82915) --- test/functional/apps/discover/_discover.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index e597cc14654bcb..3c9996ca44ff8c 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -32,7 +32,8 @@ export default function ({ getService, getPageObjects }) { defaultIndex: 'logstash-*', }; - describe('discover test', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/82915 + describe.skip('discover test', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); await esArchiver.load('discover'); From 8560b2dcc00f3992b43b75daf844ce7834678ac8 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Sun, 8 Nov 2020 18:51:31 -0500 Subject: [PATCH 53/81] [Fleet] Make stream id unique in agent policy (#82447) --- .../package_to_package_policy.test.ts | 16 +---- .../services/package_to_package_policy.ts | 18 ++--- .../common/types/models/package_policy.ts | 2 +- .../package_policy_input_config.tsx | 6 +- .../components/package_policy_input_panel.tsx | 12 ++-- .../package_policy_input_stream.tsx | 6 +- ...st..ts => validate_package_policy.test.ts} | 46 +++++------- .../services/validate_package_policy.ts | 2 +- .../step_configure_package.tsx | 9 ++- .../ingest_manager/types/index.ts | 2 + .../routes/package_policy/handlers.test.ts | 7 ++ .../server/routes/package_policy/handlers.ts | 13 +--- .../server/services/package_policy.test.ts | 6 ++ .../server/services/package_policy.ts | 71 +++++++++++++++---- .../server/types/models/package_policy.ts | 2 +- 15 files changed, 124 insertions(+), 94 deletions(-) rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/{validate_package_policy.test..ts => validate_package_policy.test.ts} (91%) diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts index 91396bce359b04..e81207300a5f31 100644 --- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts @@ -84,16 +84,14 @@ describe('Ingest Manager - packageToPackagePolicy', () => { { type: 'foo', enabled: true, - streams: [ - { id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' } }, - ], + streams: [{ enabled: true, data_stream: { dataset: 'foo', type: 'logs' } }], }, { type: 'bar', enabled: true, streams: [ - { id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' } }, - { id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } }, + { enabled: true, data_stream: { dataset: 'bar', type: 'logs' } }, + { enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } }, ], }, ]); @@ -142,7 +140,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { enabled: true, streams: [ { - id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' }, vars: { 'var-name': { value: 'foo-var-value' } }, @@ -154,13 +151,11 @@ describe('Ingest Manager - packageToPackagePolicy', () => { enabled: true, streams: [ { - id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' }, vars: { 'var-name': { type: 'text', value: 'bar-var-value' } }, }, { - id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' }, vars: { 'var-name': { type: 'yaml', value: 'bar2-var-value' } }, @@ -258,7 +253,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, streams: [ { - id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' }, vars: { @@ -276,7 +270,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, streams: [ { - id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' }, vars: { @@ -284,7 +277,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, }, { - id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' }, vars: { @@ -298,7 +290,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { enabled: false, streams: [ { - id: 'with-disabled-streams-disabled', enabled: false, data_stream: { dataset: 'disabled', type: 'logs' }, vars: { @@ -306,7 +297,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, }, { - id: 'with-disabled-streams-disabled2', enabled: false, data_stream: { dataset: 'disabled2', type: 'logs' }, }, diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts index 822747916ebc5c..cbdfa25ed7f7e1 100644 --- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts +++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts @@ -8,11 +8,10 @@ import { RegistryPolicyTemplate, RegistryVarsEntry, RegistryStream, - PackagePolicy, PackagePolicyConfigRecord, PackagePolicyConfigRecordEntry, - PackagePolicyInput, - PackagePolicyInputStream, + NewPackagePolicyInput, + NewPackagePolicyInputStream, NewPackagePolicy, } from '../types'; @@ -42,8 +41,10 @@ const getStreamsForInputType = ( /* * This service creates a package policy inputs definition from defaults provided in package info */ -export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackagePolicy['inputs'] => { - const inputs: PackagePolicy['inputs'] = []; +export const packageToPackagePolicyInputs = ( + packageInfo: PackageInfo +): NewPackagePolicy['inputs'] => { + const inputs: NewPackagePolicy['inputs'] = []; // Assume package will only ever ship one package policy template for now const packagePolicyTemplate: RegistryPolicyTemplate | null = @@ -71,12 +72,11 @@ export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackageP }; // Map each package input stream into package policy input stream - const streams: PackagePolicyInputStream[] = getStreamsForInputType( + const streams: NewPackagePolicyInputStream[] = getStreamsForInputType( packageInput.type, packageInfo ).map((packageStream) => { - const stream: PackagePolicyInputStream = { - id: `${packageInput.type}-${packageStream.data_stream.dataset}`, + const stream: NewPackagePolicyInputStream = { enabled: packageStream.enabled === false ? false : true, data_stream: packageStream.data_stream, }; @@ -86,7 +86,7 @@ export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackageP return stream; }); - const input: PackagePolicyInput = { + const input: NewPackagePolicyInput = { type: packageInput.type, enabled: streams.length ? !!streams.find((stream) => stream.enabled) : true, streams, diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts index 724dbae5dac853..ae16899a4b6f99 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts @@ -18,7 +18,6 @@ export interface PackagePolicyConfigRecordEntry { export type PackagePolicyConfigRecord = Record; export interface NewPackagePolicyInputStream { - id: string; enabled: boolean; data_stream: { dataset: string; @@ -29,6 +28,7 @@ export interface NewPackagePolicyInputStream { } export interface PackagePolicyInputStream extends NewPackagePolicyInputStream { + id: string; compiled_stream?: any; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx index 175bfb14699020..177354dad14dcc 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx @@ -14,7 +14,7 @@ import { EuiSpacer, EuiButtonEmpty, } from '@elastic/eui'; -import { PackagePolicyInput, RegistryVarsEntry } from '../../../../types'; +import { NewPackagePolicyInput, RegistryVarsEntry } from '../../../../types'; import { isAdvancedVar, PackagePolicyConfigValidationResults, @@ -28,8 +28,8 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)` export const PackagePolicyInputConfig: React.FunctionComponent<{ packageInputVars?: RegistryVarsEntry[]; - packagePolicyInput: PackagePolicyInput; - updatePackagePolicyInput: (updatedInput: Partial) => void; + packagePolicyInput: NewPackagePolicyInput; + updatePackagePolicyInput: (updatedInput: Partial) => void; inputVarsValidationResults: PackagePolicyConfigValidationResults; forceShowErrors?: boolean; }> = memo( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx index 1e43cc0d5938ee..79ff0cc29850c8 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { - PackagePolicyInput, + NewPackagePolicyInput, PackagePolicyInputStream, RegistryInput, RegistryStream, @@ -40,7 +40,7 @@ const ShortenedHorizontalRule = styled(EuiHorizontalRule)` const shouldShowStreamsByDefault = ( packageInput: RegistryInput, packageInputStreams: Array, - packagePolicyInput: PackagePolicyInput + packagePolicyInput: NewPackagePolicyInput ): boolean => { return ( packagePolicyInput.enabled && @@ -63,8 +63,8 @@ const shouldShowStreamsByDefault = ( export const PackagePolicyInputPanel: React.FunctionComponent<{ packageInput: RegistryInput; packageInputStreams: Array; - packagePolicyInput: PackagePolicyInput; - updatePackagePolicyInput: (updatedInput: Partial) => void; + packagePolicyInput: NewPackagePolicyInput; + updatePackagePolicyInput: (updatedInput: Partial) => void; inputValidationResults: PackagePolicyInputValidationResults; forceShowErrors?: boolean; }> = memo( @@ -210,7 +210,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ ...updatedStream, }; - const updatedInput: Partial = { + const updatedInput: Partial = { streams: newStreams, }; @@ -227,7 +227,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ updatePackagePolicyInput(updatedInput); }} inputStreamValidationResults={ - inputValidationResults.streams![packagePolicyInputStream!.id] + inputValidationResults.streams![packagePolicyInputStream!.data_stream!.dataset] } forceShowErrors={forceShowErrors} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx index 3d33edd468151d..963d0da50ce7f9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx @@ -16,7 +16,7 @@ import { EuiSpacer, EuiButtonEmpty, } from '@elastic/eui'; -import { PackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; +import { NewPackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; import { isAdvancedVar, PackagePolicyConfigValidationResults, @@ -30,8 +30,8 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)` export const PackagePolicyInputStreamConfig: React.FunctionComponent<{ packageInputStream: RegistryStream; - packagePolicyInputStream: PackagePolicyInputStream; - updatePackagePolicyInputStream: (updatedStream: Partial) => void; + packagePolicyInputStream: NewPackagePolicyInputStream; + updatePackagePolicyInputStream: (updatedStream: Partial) => void; inputStreamValidationResults: PackagePolicyConfigValidationResults; forceShowErrors?: boolean; }> = memo( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts similarity index 91% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts rename to x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts index 9022e312ece79c..8d46fed1ff14e2 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts @@ -154,7 +154,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'foo-foo', data_stream: { dataset: 'foo', type: 'logs' }, enabled: true, vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, @@ -170,13 +169,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'bar-bar', data_stream: { dataset: 'bar', type: 'logs' }, enabled: true, vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, }, { - id: 'bar-bar2', data_stream: { dataset: 'bar2', type: 'logs' }, enabled: true, vars: { 'var-name': { value: undefined, type: 'text' } }, @@ -193,13 +190,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => { enabled: true, streams: [ { - id: 'with-disabled-streams-disabled', data_stream: { dataset: 'disabled', type: 'logs' }, enabled: false, vars: { 'var-name': { value: undefined, type: 'text' } }, }, { - id: 'with-disabled-streams-disabled-without-vars', data_stream: { dataset: 'disabled2', type: 'logs' }, enabled: false, }, @@ -213,8 +208,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'with-no-stream-vars-bar', - data_stream: { dataset: 'bar', type: 'logs' }, + data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, enabled: true, }, ], @@ -236,7 +230,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'foo-foo', data_stream: { dataset: 'foo', type: 'logs' }, enabled: true, vars: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } }, @@ -252,13 +245,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'bar-bar', data_stream: { dataset: 'bar', type: 'logs' }, enabled: true, vars: { 'var-name': { value: ' \n\n', type: 'yaml' } }, }, { - id: 'bar-bar2', data_stream: { dataset: 'bar2', type: 'logs' }, enabled: true, vars: { 'var-name': { value: undefined, type: 'text' } }, @@ -275,7 +266,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { enabled: true, streams: [ { - id: 'with-disabled-streams-disabled', data_stream: { dataset: 'disabled', type: 'logs' }, enabled: false, vars: { @@ -286,7 +276,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, }, { - id: 'with-disabled-streams-disabled-without-vars', data_stream: { dataset: 'disabled2', type: 'logs' }, enabled: false, }, @@ -300,8 +289,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'with-no-stream-vars-bar', - data_stream: { dataset: 'bar', type: 'logs' }, + data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, enabled: true, }, ], @@ -320,21 +308,21 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'foo-input2-var-name': null, 'foo-input3-var-name': null, }, - streams: { 'foo-foo': { vars: { 'var-name': null } } }, + streams: { foo: { vars: { 'var-name': null } } }, }, bar: { vars: { 'bar-input-var-name': null, 'bar-input2-var-name': null }, streams: { - 'bar-bar': { vars: { 'var-name': null } }, - 'bar-bar2': { vars: { 'var-name': null } }, + bar: { vars: { 'var-name': null } }, + bar2: { vars: { 'var-name': null } }, }, }, 'with-disabled-streams': { streams: { - 'with-disabled-streams-disabled': { + disabled: { vars: { 'var-name': null }, }, - 'with-disabled-streams-disabled-without-vars': {}, + disabled2: {}, }, }, 'with-no-stream-vars': { @@ -364,7 +352,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'foo-input2-var-name': ['foo-input2-var-name is required'], 'foo-input3-var-name': ['foo-input3-var-name is required'], }, - streams: { 'foo-foo': { vars: { 'var-name': ['Invalid YAML format'] } } }, + streams: { foo: { vars: { 'var-name': ['Invalid YAML format'] } } }, }, bar: { vars: { @@ -372,14 +360,14 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'bar-input2-var-name': ['bar-input2-var-name is required'], }, streams: { - 'bar-bar': { vars: { 'var-name': ['var-name is required'] } }, - 'bar-bar2': { vars: { 'var-name': null } }, + bar: { vars: { 'var-name': ['var-name is required'] } }, + bar2: { vars: { 'var-name': null } }, }, }, 'with-disabled-streams': { streams: { - 'with-disabled-streams-disabled': { vars: { 'var-name': null } }, - 'with-disabled-streams-disabled-without-vars': {}, + disabled: { vars: { 'var-name': null } }, + disabled2: {}, }, }, 'with-no-stream-vars': { @@ -427,7 +415,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'foo-input2-var-name': ['foo-input2-var-name is required'], 'foo-input3-var-name': ['foo-input3-var-name is required'], }, - streams: { 'foo-foo': { vars: { 'var-name': null } } }, + streams: { foo: { vars: { 'var-name': null } } }, }, bar: { vars: { @@ -435,16 +423,16 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'bar-input2-var-name': ['bar-input2-var-name is required'], }, streams: { - 'bar-bar': { vars: { 'var-name': null } }, - 'bar-bar2': { vars: { 'var-name': null } }, + bar: { vars: { 'var-name': null } }, + bar2: { vars: { 'var-name': null } }, }, }, 'with-disabled-streams': { streams: { - 'with-disabled-streams-disabled': { + disabled: { vars: { 'var-name': null }, }, - 'with-disabled-streams-disabled-without-vars': {}, + disabled2: {}, }, }, 'with-no-stream-vars': { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts index 9ce73c0690ccbe..1126cd7e58e186 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts @@ -151,7 +151,7 @@ export const validatePackagePolicy = ( ); } - inputValidationResults.streams![stream.id] = streamValidationResults; + inputValidationResults.streams![stream.data_stream.dataset] = streamValidationResults; }); } else { delete inputValidationResults.streams; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index d3d5e60c34e584..b335ff439684bb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -5,7 +5,12 @@ */ import React from 'react'; import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { PackageInfo, RegistryStream, NewPackagePolicy, PackagePolicyInput } from '../../../types'; +import { + PackageInfo, + RegistryStream, + NewPackagePolicy, + NewPackagePolicyInput, +} from '../../../types'; import { Loading } from '../../../components'; import { PackagePolicyValidationResults } from './services'; import { PackagePolicyInputPanel, CustomPackagePolicy } from './components'; @@ -71,7 +76,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ packageInput={packageInput} packageInputStreams={packageInputStreams} packagePolicyInput={packagePolicyInput} - updatePackagePolicyInput={(updatedInput: Partial) => { + updatePackagePolicyInput={(updatedInput: Partial) => { const indexOfUpdatedInput = packagePolicy.inputs.findIndex( (input) => input.type === packageInput.type ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 386ffa5649cc23..1cf8077aeda404 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -17,7 +17,9 @@ export { NewPackagePolicy, UpdatePackagePolicy, PackagePolicyInput, + NewPackagePolicyInput, PackagePolicyInputStream, + NewPackagePolicyInputStream, PackagePolicyConfigRecord, PackagePolicyConfigRecordEntry, Output, diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts index 44c2ccda3bd2a5..f47b8499a1b69e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts @@ -28,6 +28,13 @@ jest.mock('../../services/package_policy', (): { create: jest.fn((soClient, callCluster, newData) => Promise.resolve({ ...newData, + inputs: newData.inputs.map((input) => ({ + ...input, + streams: input.streams.map((stream) => ({ + id: stream.data_stream.dataset, + ...stream, + })), + })), id: '1', revision: 1, updated_at: new Date().toISOString(), diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts index d9baeca4deb471..3a2b9ba7a744f7 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts @@ -7,7 +7,6 @@ import { TypeOf } from '@kbn/config-schema'; import Boom from '@hapi/boom'; import { RequestHandler, SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; import { appContextService, packagePolicyService } from '../../services'; -import { getPackageInfo } from '../../services/epm/packages'; import { GetPackagePoliciesRequestSchema, GetOnePackagePolicyRequestSchema, @@ -134,21 +133,11 @@ export const updatePackagePolicyHandler: RequestHandler< const newData = { ...request.body }; const pkg = newData.package || packagePolicy.package; const inputs = newData.inputs || packagePolicy.inputs; - if (pkg && (newData.inputs || newData.package)) { - const pkgInfo = await getPackageInfo({ - savedObjectsClient: soClient, - pkgName: pkg.name, - pkgVersion: pkg.version, - }); - newData.inputs = (await packagePolicyService.assignPackageStream(pkgInfo, inputs)) as TypeOf< - typeof CreatePackagePolicyRequestSchema.body - >['inputs']; - } const updatedPackagePolicy = await packagePolicyService.update( soClient, request.params.packagePolicyId, - newData, + { ...newData, package: pkg, inputs }, { user } ); return response.ok({ diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts index 6064e5bae06348..6ae76c56436d57 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts @@ -34,6 +34,12 @@ jest.mock('./epm/packages/assets', () => { }; }); +jest.mock('./epm/packages', () => { + return { + getPackageInfo: () => ({}), + }; +}); + jest.mock('./epm/registry', () => { return { fetchInfo: () => ({}), diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.ts index dc3a4495191c96..0f78c97a6f2bdc 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_policy.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsClientContract } from 'src/core/server'; +import uuid from 'uuid'; import { AuthenticatedUser } from '../../../security/server'; import { DeletePackagePoliciesResponse, PackagePolicyInput, + NewPackagePolicyInput, PackagePolicyInputStream, PackageInfo, ListWithKuery, @@ -58,6 +60,11 @@ class PackagePolicyService { throw new Error('There is already a package with the same name on this agent policy'); } } + // Add ids to stream + const packagePolicyId = options?.id || uuid.v4(); + let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) => + assignStreamIdToInput(packagePolicyId, input) + ); // Make sure the associated package is installed if (packagePolicy.package?.name) { @@ -85,7 +92,7 @@ class PackagePolicyService { } } - packagePolicy.inputs = await this.assignPackageStream(pkgInfo, packagePolicy.inputs); + inputs = await this.assignPackageStream(pkgInfo, inputs); } const isoDate = new Date().toISOString(); @@ -93,13 +100,15 @@ class PackagePolicyService { SAVED_OBJECT_TYPE, { ...packagePolicy, + inputs, revision: 1, created_at: isoDate, created_by: options?.user?.username ?? 'system', updated_at: isoDate, updated_by: options?.user?.username ?? 'system', }, - options + + { ...options, id: packagePolicyId } ); // Assign it to the given agent policy @@ -124,18 +133,28 @@ class PackagePolicyService { const isoDate = new Date().toISOString(); // eslint-disable-next-line @typescript-eslint/naming-convention const { saved_objects } = await soClient.bulkCreate( - packagePolicies.map((packagePolicy) => ({ - type: SAVED_OBJECT_TYPE, - attributes: { - ...packagePolicy, - policy_id: agentPolicyId, - revision: 1, - created_at: isoDate, - created_by: options?.user?.username ?? 'system', - updated_at: isoDate, - updated_by: options?.user?.username ?? 'system', - }, - })) + packagePolicies.map((packagePolicy) => { + const packagePolicyId = uuid.v4(); + + const inputs = packagePolicy.inputs.map((input) => + assignStreamIdToInput(packagePolicyId, input) + ); + + return { + type: SAVED_OBJECT_TYPE, + id: packagePolicyId, + attributes: { + ...packagePolicy, + inputs, + policy_id: agentPolicyId, + revision: 1, + created_at: isoDate, + created_by: options?.user?.username ?? 'system', + updated_at: isoDate, + updated_by: options?.user?.username ?? 'system', + }, + }; + }) ); // Filter out invalid SOs @@ -255,11 +274,26 @@ class PackagePolicyService { } } + let inputs = await restOfPackagePolicy.inputs.map((input) => + assignStreamIdToInput(oldPackagePolicy.id, input) + ); + + if (packagePolicy.package?.name) { + const pkgInfo = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + pkgVersion: packagePolicy.package.version, + }); + + inputs = await this.assignPackageStream(pkgInfo, inputs); + } + await soClient.update( SAVED_OBJECT_TYPE, id, { ...restOfPackagePolicy, + inputs, revision: oldPackagePolicy.revision + 1, updated_at: new Date().toISOString(), updated_by: options?.user?.username ?? 'system', @@ -353,6 +387,15 @@ class PackagePolicyService { } } +function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyInput) { + return { + ...input, + streams: input.streams.map((stream) => { + return { ...stream, id: `${input.type}-${stream.data_stream.dataset}-${packagePolicyId}` }; + }), + }; +} + async function _assignPackageStreamToInput( registryPkgInfo: RegistryPackage, pkgInfo: PackageInfo, diff --git a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts index 6673c12d515117..20d29c0aa18c9a 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts @@ -54,7 +54,7 @@ const PackagePolicyBaseSchema = { ), streams: schema.arrayOf( schema.object({ - id: schema.string(), + id: schema.maybe(schema.string()), // BWC < 7.11 enabled: schema.boolean(), data_stream: schema.object({ dataset: schema.string(), type: schema.string() }), vars: schema.maybe(ConfigRecordSchema), From 3e211e938bc4c110fd4ab21e38f2b3f133a546ee Mon Sep 17 00:00:00 2001 From: John Schulz Date: Mon, 9 Nov 2020 05:42:45 -0500 Subject: [PATCH 54/81] [Ingest Manager] Unify install* under installPackage (#82916) ## Summary * Add `installPackage` with `installSource` param, to provide a single interface the `install*` functions. ```diff - const res = await installPackageFromRegistry({ + const res = await installPackage({ + installSource: 'registry', ``` and ```diff - const res = await installPackageByUpload({ + const res = await installPackage({ + installSource: 'upload', ``` * Push some repeated work (`install`, `removable`) from `install*` into `_installPackage`. Which also simplifies its interface. ### installPackage For now `installPackage` checks the `installSource` and calls the same `install*` functions to prevent any change in behavior but there's still a lot of overlap between `installPackageFromRegistry` & `installPackageByUpload`. I think we can bring them together into `installPackage` using the same branching on `installSource`. ### local checks with curl
curl request/responses for happy path: ``` ## zip: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"response":[{"id":"apache-Logs-Apache-Dashboard-ecs","type":"dashboard"},{"id":"apache-Metrics-Apache-HTTPD-server-status-ecs","type":"dashboard"},{"id":"Apache-HTTPD-CPU-ecs","type":"visualization"},{"id":"Apache-HTTPD-Hostname-list-ecs","type":"visualization"},{"id":"Apache-HTTPD-Load1-slash-5-slash-15-ecs","type":"visualization"},{"id":"Apache-HTTPD-Scoreboard-ecs","type":"visualization"},{"id":"Apache-HTTPD-Total-accesses-and-kbytes-ecs","type":"visualization"},{"id":"Apache-HTTPD-Uptime-ecs","type":"visualization"},{"id":"Apache-HTTPD-Workers-ecs","type":"visualization"},{"id":"Apache-access-unique-IPs-map-ecs","type":"visualization"},{"id":"Apache-browsers-ecs","type":"visualization"},{"id":"Apache-error-logs-over-time-ecs","type":"visualization"},{"id":"Apache-operating-systems-ecs","type":"visualization"},{"id":"Apache-response-codes-of-top-URLs-ecs","type":"visualization"},{"id":"Apache-response-codes-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-ecs","type":"search"},{"id":"Apache-access-logs-ecs","type":"search"},{"id":"Apache-errors-log-ecs","type":"search"}]} ## Uploaded packages can be deleted as expected: curl -X DELETE -u elastic:changeme http://localhost:5601/api/fleet/epm/packages/apache-0.1.4 -H 'kbn-xsrf: xxx' {"response":[{"id":"apache-Logs-Apache-Dashboard-ecs","type":"dashboard"},{"id":"apache-Metrics-Apache-HTTPD-server-status-ecs","type":"dashboard"},{"id":"Apache-HTTPD-CPU-ecs","type":"visualization"},{"id":"Apache-HTTPD-Hostname-list-ecs","type":"visualization"},{"id":"Apache-HTTPD-Load1-slash-5-slash-15-ecs","type":"visualization"},{"id":"Apache-HTTPD-Scoreboard-ecs","type":"visualization"},{"id":"Apache-HTTPD-Total-accesses-and-kbytes-ecs","type":"visualization"},{"id":"Apache-HTTPD-Uptime-ecs","type":"visualization"},{"id":"Apache-HTTPD-Workers-ecs","type":"visualization"},{"id":"Apache-access-unique-IPs-map-ecs","type":"visualization"},{"id":"Apache-browsers-ecs","type":"visualization"},{"id":"Apache-error-logs-over-time-ecs","type":"visualization"},{"id":"Apache-operating-systems-ecs","type":"visualization"},{"id":"Apache-response-codes-of-top-URLs-ecs","type":"visualization"},{"id":"Apache-response-codes-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-ecs","type":"search"},{"id":"Apache-access-logs-ecs","type":"search"},{"id":"Apache-errors-log-ecs","type":"search"}]} ## Now upload curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz -H 'kbn-xsrf: xyz' -H 'Content-Type: application/gzip' {"response":[{"id":"apache-Metrics-Apache-HTTPD-server-status-ecs","type":"dashboard"},{"id":"apache-Logs-Apache-Dashboard-ecs","type":"dashboard"},{"id":"Apache-access-unique-IPs-map-ecs","type":"visualization"},{"id":"Apache-HTTPD-CPU-ecs","type":"visualization"},{"id":"Apache-HTTPD-Load1-slash-5-slash-15-ecs","type":"visualization"},{"id":"Apache-response-codes-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-Workers-ecs","type":"visualization"},{"id":"Apache-HTTPD-Hostname-list-ecs","type":"visualization"},{"id":"Apache-error-logs-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-Scoreboard-ecs","type":"visualization"},{"id":"Apache-HTTPD-Uptime-ecs","type":"visualization"},{"id":"Apache-operating-systems-ecs","type":"visualization"},{"id":"Apache-HTTPD-Total-accesses-and-kbytes-ecs","type":"visualization"},{"id":"Apache-browsers-ecs","type":"visualization"},{"id":"Apache-response-codes-of-top-URLs-ecs","type":"visualization"},{"id":"Apache-access-logs-ecs","type":"search"},{"id":"Apache-errors-log-ecs","type":"search"},{"id":"Apache-HTTPD-ecs","type":"search"},{"id":"logs-apache.error-0.1.4","type":"ingest_pipeline"},{"id":"logs-apache.access-0.1.4","type":"ingest_pipeline"},{"id":"logs-apache.error","type":"index_template"},{"id":"metrics-apache.status","type":"index_template"},{"id":"logs-apache.access","type":"index_template"}]} ```
curl request/responses for archive errors: ``` ## Wrong content type: ### tar.gz with application/zip: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Error during extraction of package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."} ### zip with application/gzip: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/gzip' {"statusCode":400,"error":"Bad Request","message":"Archive seems empty. Assumed content type was application/gzip, check if this matches the archive type."} ## Invalid packages ### Two top-level directories: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_two_toplevels_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Package contains more than one top-level directory."} ### No manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_no_manifest_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Package must contain a top-level manifest.yml file."} ### Invalid YAML in manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_manifest_invalid_yaml_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Could not parse top-level package manifest: YAMLException: bad indentation of a mapping entry at line 2, column 7:\n name: apache\n ^."} ### Mandatory field missing in manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_manifest_missing_field_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version"} ### Top-level directory doesn't match name and version from manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_toplevel_mismatch_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Name thisIsATypo and version 0.1.4 do not match top-level directory apache-0.1.4"} ```
#### TS type check examples on `installPackage`
screenshots Screen Shot 2020-11-08 at 4 00 14 PM Screen Shot 2020-11-08 at 4 00 21 PM Screen Shot 2020-11-08 at 4 01 06 PM Screen Shot 2020-11-08 at 4 01 25 PM Screen Shot 2020-11-08 at 4 02 54 PM
### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../server/routes/epm/handlers.ts | 9 +- .../epm/packages/_install_package.test.ts | 4 - .../services/epm/packages/_install_package.ts | 11 +-- .../server/services/epm/packages/index.ts | 3 +- .../server/services/epm/packages/install.ts | 92 ++++++++++++------- 5 files changed, 68 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts index 198a54ca84125b..1d221b8b1eeadd 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts @@ -35,8 +35,7 @@ import { getPackageInfo, handleInstallPackageFailure, isBulkInstallError, - installPackageFromRegistry, - installPackageByUpload, + installPackage, removeInstallation, getLimitedPackages, getInstallationObject, @@ -149,7 +148,8 @@ export const installPackageFromRegistryHandler: RequestHandler< const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); try { - const res = await installPackageFromRegistry({ + const res = await installPackage({ + installSource: 'registry', savedObjectsClient, pkgkey, callCluster, @@ -224,7 +224,8 @@ export const installPackageByUploadHandler: RequestHandler< const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later const archiveBuffer = Buffer.from(request.body); try { - const res = await installPackageByUpload({ + const res = await installPackage({ + installSource: 'upload', savedObjectsClient, callCluster, archiveBuffer, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts index 5d3e8e9ce87d1b..b7650d10b6b250 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts @@ -61,11 +61,7 @@ describe('_installPackage', () => { const installationPromise = _installPackage({ savedObjectsClient: soClient, callCluster, - pkgName: 'abc', - pkgVersion: '1.2.3', paths: [], - removable: false, - internal: false, packageInfo: { name: 'xyz', version: '4.5.6', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts index f570984cc61aac..a83d9428b7c939 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts @@ -21,6 +21,7 @@ import { installPipelines, deletePreviousPipelines } from '../elasticsearch/inge import { installILMPolicy } from '../elasticsearch/ilm/install'; import { installKibanaAssets, getKibanaAssets } from '../kibana/assets/install'; import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; +import { isRequiredPackage } from './index'; import { deleteKibanaSavedObjectsAssets } from './remove'; import { installTransform } from '../elasticsearch/transform/install'; import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install'; @@ -32,28 +33,22 @@ import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './insta export async function _installPackage({ savedObjectsClient, callCluster, - pkgName, - pkgVersion, installedPkg, paths, - removable, - internal, packageInfo, installType, installSource, }: { savedObjectsClient: SavedObjectsClientContract; callCluster: CallESAsCurrentUser; - pkgName: string; - pkgVersion: string; installedPkg?: SavedObject; paths: string[]; - removable: boolean; - internal: boolean; packageInfo: InstallablePackage; installType: InstallType; installSource: InstallSource; }): Promise { + const { internal = false, name: pkgName, version: pkgVersion } = packageInfo; + const removable = !isRequiredPackage(pkgName); const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams); // add the package installation to the saved object. // if some installation already exists, just update install info diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts index 410a9c0b225371..a1128011d81e6a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts @@ -29,8 +29,7 @@ export { BulkInstallResponse, IBulkInstallPackageError, handleInstallPackageFailure, - installPackageFromRegistry, - installPackageByUpload, + installPackage, ensureInstalledPackage, } from './install'; export { removeInstallation } from './remove'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index e7d8c8d4695d4e..00a5c689e906d7 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -24,7 +24,6 @@ import * as Registry from '../registry'; import { getInstallation, getInstallationObject, - isRequiredPackage, bulkInstallPackages, isBulkInstallError, } from './index'; @@ -52,7 +51,7 @@ export async function installLatestPackage(options: { name: latestPackage.name, version: latestPackage.version, }); - return installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }); + return installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster }); } catch (err) { throw err; } @@ -148,7 +147,8 @@ export async function handleInstallPackageFailure({ } const prevVersion = `${pkgName}-${installedPkg.attributes.version}`; logger.error(`rolling back to ${prevVersion} after error installing ${pkgkey}`); - await installPackageFromRegistry({ + await installPackage({ + installSource: 'registry', savedObjectsClient, pkgkey: prevVersion, callCluster, @@ -186,7 +186,12 @@ export async function upgradePackage({ }); try { - const assets = await installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }); + const assets = await installPackage({ + installSource: 'registry', + savedObjectsClient, + pkgkey, + callCluster, + }); return { name: pkgToUpgrade, newVersion: latestPkg.version, @@ -218,19 +223,19 @@ export async function upgradePackage({ } } -interface InstallPackageParams { +interface InstallRegistryPackageParams { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; callCluster: CallESAsCurrentUser; force?: boolean; } -export async function installPackageFromRegistry({ +async function installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster, force = false, -}: InstallPackageParams): Promise { +}: InstallRegistryPackageParams): Promise { // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge @@ -250,37 +255,36 @@ export async function installPackageFromRegistry({ const { paths, registryPackageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); - const removable = !isRequiredPackage(pkgName); - const { internal = false } = registryPackageInfo; - const installSource = 'registry'; - return _installPackage({ savedObjectsClient, callCluster, - pkgName, - pkgVersion, installedPkg, paths, - removable, - internal, packageInfo: registryPackageInfo, installType, - installSource, + installSource: 'registry', }); } -export async function installPackageByUpload({ - savedObjectsClient, - callCluster, - archiveBuffer, - contentType, -}: { +interface InstallUploadedArchiveParams { savedObjectsClient: SavedObjectsClientContract; callCluster: CallESAsCurrentUser; archiveBuffer: Buffer; contentType: string; -}): Promise { +} + +export type InstallPackageParams = + | ({ installSource: Extract } & InstallRegistryPackageParams) + | ({ installSource: Extract } & InstallUploadedArchiveParams); + +async function installPackageByUpload({ + savedObjectsClient, + callCluster, + archiveBuffer, + contentType, +}: InstallUploadedArchiveParams): Promise { const { paths, archivePackageInfo } = await loadArchivePackage({ archiveBuffer, contentType }); + const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName: archivePackageInfo.name, @@ -292,25 +296,45 @@ export async function installPackageByUpload({ ); } - const removable = !isRequiredPackage(archivePackageInfo.name); - const { internal = false } = archivePackageInfo; - const installSource = 'upload'; - return _installPackage({ savedObjectsClient, callCluster, - pkgName: archivePackageInfo.name, - pkgVersion: archivePackageInfo.version, installedPkg, paths, - removable, - internal, packageInfo: archivePackageInfo, installType, - installSource, + installSource: 'upload', }); } +export async function installPackage(args: InstallPackageParams) { + if (!('installSource' in args)) { + throw new Error('installSource is required'); + } + + if (args.installSource === 'registry') { + const { savedObjectsClient, pkgkey, callCluster, force } = args; + + return installPackageFromRegistry({ + savedObjectsClient, + pkgkey, + callCluster, + force, + }); + } else if (args.installSource === 'upload') { + const { savedObjectsClient, callCluster, archiveBuffer, contentType } = args; + + return installPackageByUpload({ + savedObjectsClient, + callCluster, + archiveBuffer, + contentType, + }); + } + // @ts-expect-error s/b impossibe b/c `never` by this point, but just in case + throw new Error(`Unknown installSource: ${args.installSource}`); +} + export const updateVersion = async ( savedObjectsClient: SavedObjectsClientContract, pkgName: string, @@ -421,7 +445,9 @@ export async function ensurePackagesCompletedInstall( const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`; // reinstall package if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) { - acc.push(installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster })); + acc.push( + installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster }) + ); } return acc; }, []); From fdc18392ad2c70b92fbfb4f69e1af892c020efab Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 9 Nov 2020 11:51:14 +0100 Subject: [PATCH 55/81] SavedObjects search_dsl: add match_phrase_prefix clauses when using prefix search (#82693) * add match_phrase_prefix clauses when using prefix search * add FTR tests --- .../lib/search_dsl/query_params.test.ts | 473 ++++++++++++------ .../service/lib/search_dsl/query_params.ts | 189 +++++-- .../service/lib/search_dsl/search_dsl.test.ts | 1 - .../service/lib/search_dsl/search_dsl.ts | 1 - .../apis/saved_objects/find.js | 64 +++ .../saved_objects/find_edgecases/data.json | 93 ++++ .../find_edgecases/mappings.json | 267 ++++++++++ 7 files changed, 883 insertions(+), 205 deletions(-) create mode 100644 test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json create mode 100644 test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 333f5caf725258..a8c5df8d646305 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -21,28 +21,64 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; -import { typeRegistryMock } from '../../../saved_objects_type_registry.mock'; +import { SavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING } from '../utils'; import { getQueryParams, getClauseForReference } from './query_params'; -const registry = typeRegistryMock.create(); +const registerTypes = (registry: SavedObjectTypeRegistry) => { + registry.registerType({ + name: 'pending', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { title: { type: 'text' } }, + }, + management: { + defaultSearchField: 'title', + }, + }); -const MAPPINGS = { - properties: { - pending: { properties: { title: { type: 'text' } } }, - saved: { + registry.registerType({ + name: 'saved', + hidden: false, + namespaceType: 'single', + mappings: { properties: { title: { type: 'text', fields: { raw: { type: 'keyword' } } }, obj: { properties: { key1: { type: 'text' } } }, }, }, - // mock registry returns isMultiNamespace=true for 'shared' type - shared: { properties: { name: { type: 'keyword' } } }, - // mock registry returns isNamespaceAgnostic=true for 'global' type - global: { properties: { name: { type: 'keyword' } } }, - }, + management: { + defaultSearchField: 'title', + }, + }); + + registry.registerType({ + name: 'shared', + hidden: false, + namespaceType: 'multiple', + mappings: { + properties: { name: { type: 'keyword' } }, + }, + management: { + defaultSearchField: 'name', + }, + }); + + registry.registerType({ + name: 'global', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { name: { type: 'keyword' } }, + }, + management: { + defaultSearchField: 'name', + }, + }); }; -const ALL_TYPES = Object.keys(MAPPINGS.properties); + +const ALL_TYPES = ['pending', 'saved', 'shared', 'global']; // get all possible subsets (combination) of all types const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( (subsets, value) => subsets.concat(subsets.map((set) => [...set, value])), @@ -51,48 +87,53 @@ const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( .filter((x) => x.length) // exclude empty set .map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it -const createTypeClause = (type: string, namespaces?: string[]) => { - if (registry.isMultiNamespace(type)) { - const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; - return { - bool: { - must: expect.arrayContaining([{ terms: { namespaces: array } }]), - must_not: [{ exists: { field: 'namespace' } }], - }, - }; - } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; - const should: any = []; - if (nonDefaultNamespaces.length > 0) { - should.push({ terms: { namespace: nonDefaultNamespaces } }); - } - if (namespaces?.includes('default')) { - should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); - } - return { - bool: { - must: [{ term: { type } }], - should: expect.arrayContaining(should), - minimum_should_match: 1, - must_not: [{ exists: { field: 'namespaces' } }], - }, - }; - } - // isNamespaceAgnostic - return { - bool: expect.objectContaining({ - must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], - }), - }; -}; - /** * Note: these tests cases are defined in the order they appear in the source code, for readability's sake */ describe('#getQueryParams', () => { - const mappings = MAPPINGS; + let registry: SavedObjectTypeRegistry; type Result = ReturnType; + beforeEach(() => { + registry = new SavedObjectTypeRegistry(); + registerTypes(registry); + }); + + const createTypeClause = (type: string, namespaces?: string[]) => { + if (registry.isMultiNamespace(type)) { + const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; + return { + bool: { + must: expect.arrayContaining([{ terms: { namespaces: array } }]), + must_not: [{ exists: { field: 'namespace' } }], + }, + }; + } else if (registry.isSingleNamespace(type)) { + const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const should: any = []; + if (nonDefaultNamespaces.length > 0) { + should.push({ terms: { namespace: nonDefaultNamespaces } }); + } + if (namespaces?.includes('default')) { + should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); + } + return { + bool: { + must: [{ term: { type } }], + should: expect.arrayContaining(should), + minimum_should_match: 1, + must_not: [{ exists: { field: 'namespaces' } }], + }, + }; + } + // isNamespaceAgnostic + return { + bool: expect.objectContaining({ + must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], + }), + }; + }; + describe('kueryNode filter clause', () => { const expectResult = (result: Result, expected: any) => { expect(result.query.bool.filter).toEqual(expect.arrayContaining([expected])); @@ -100,13 +141,13 @@ describe('#getQueryParams', () => { describe('`kueryNode` parameter', () => { it('does not include the clause when `kueryNode` is not specified', () => { - const result = getQueryParams({ mappings, registry, kueryNode: undefined }); + const result = getQueryParams({ registry, kueryNode: undefined }); expect(result.query.bool.filter).toHaveLength(1); }); it('includes the specified Kuery clause', () => { const test = (kueryNode: KueryNode) => { - const result = getQueryParams({ mappings, registry, kueryNode }); + const result = getQueryParams({ registry, kueryNode }); const expected = esKuery.toElasticsearchQuery(kueryNode); expect(result.query.bool.filter).toHaveLength(2); expectResult(result, expected); @@ -165,7 +206,6 @@ describe('#getQueryParams', () => { it('does not include the clause when `hasReference` is not specified', () => { const result = getQueryParams({ - mappings, registry, hasReference: undefined, }); @@ -176,7 +216,6 @@ describe('#getQueryParams', () => { it('creates a should clause for specified reference when operator is `OR`', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'OR', @@ -192,7 +231,6 @@ describe('#getQueryParams', () => { it('creates a must clause for specified reference when operator is `AND`', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'AND', @@ -210,7 +248,6 @@ describe('#getQueryParams', () => { { id: 'hello', type: 'dolly' }, ]; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'OR', @@ -229,7 +266,6 @@ describe('#getQueryParams', () => { { id: 'hello', type: 'dolly' }, ]; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'AND', @@ -244,7 +280,6 @@ describe('#getQueryParams', () => { it('defaults to `OR` when operator is not specified', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, }); @@ -278,14 +313,13 @@ describe('#getQueryParams', () => { }; it('searches for all known types when `type` is not specified', () => { - const result = getQueryParams({ mappings, registry, type: undefined }); + const result = getQueryParams({ registry, type: undefined }); expectResult(result, ...ALL_TYPES); }); it('searches for specified type/s', () => { const test = (typeOrTypes: string | string[]) => { const result = getQueryParams({ - mappings, registry, type: typeOrTypes, }); @@ -309,18 +343,17 @@ describe('#getQueryParams', () => { const test = (namespaces?: string[]) => { for (const typeOrTypes of ALL_TYPE_SUBSETS) { - const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespaces }); + const result = getQueryParams({ registry, type: typeOrTypes, namespaces }); const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; expectResult(result, ...types.map((x) => createTypeClause(x, namespaces))); } // also test with no specified type/s - const result = getQueryParams({ mappings, registry, type: undefined, namespaces }); + const result = getQueryParams({ registry, type: undefined, namespaces }); expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces))); }; it('normalizes and deduplicates provided namespaces', () => { const result = getQueryParams({ - mappings, registry, search: '*', namespaces: ['foo', '*', 'foo', 'bar', 'default'], @@ -360,7 +393,6 @@ describe('#getQueryParams', () => { it('supersedes `type` and `namespaces` parameters', () => { const result = getQueryParams({ - mappings, registry, type: ['pending', 'saved', 'shared', 'global'], namespaces: ['foo', 'bar', 'default'], @@ -381,148 +413,266 @@ describe('#getQueryParams', () => { }); }); - describe('search clause (query.bool.must.simple_query_string)', () => { - const search = 'foo*'; + describe('search clause (query.bool)', () => { + describe('when using simple search (query.bool.must.simple_query_string)', () => { + const search = 'foo'; - const expectResult = (result: Result, sqsClause: any) => { - expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]); - }; + const expectResult = (result: Result, sqsClause: any) => { + expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]); + }; - describe('`search` parameter', () => { - it('does not include clause when `search` is not specified', () => { - const result = getQueryParams({ - mappings, - registry, - search: undefined, + describe('`search` parameter', () => { + it('does not include clause when `search` is not specified', () => { + const result = getQueryParams({ + registry, + search: undefined, + }); + expect(result.query.bool.must).toBeUndefined(); }); - expect(result.query.bool.must).toBeUndefined(); - }); - it('creates a clause with query for specified search', () => { - const result = getQueryParams({ - mappings, - registry, - search, + it('creates a clause with query for specified search', () => { + const result = getQueryParams({ + registry, + search, + }); + expectResult(result, expect.objectContaining({ query: search })); }); - expectResult(result, expect.objectContaining({ query: search })); }); - }); - describe('`searchFields` and `rootSearchFields` parameters', () => { - const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { - const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; - return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); - }; + describe('`searchFields` and `rootSearchFields` parameters', () => { + const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { + const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; + return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); + }; - const test = ({ - searchFields, - rootSearchFields, - }: { - searchFields?: string[]; - rootSearchFields?: string[]; - }) => { - for (const typeOrTypes of ALL_TYPE_SUBSETS) { + const test = ({ + searchFields, + rootSearchFields, + }: { + searchFields?: string[]; + rootSearchFields?: string[]; + }) => { + for (const typeOrTypes of ALL_TYPE_SUBSETS) { + const result = getQueryParams({ + registry, + type: typeOrTypes, + search, + searchFields, + rootSearchFields, + }); + let fields = rootSearchFields || []; + if (searchFields) { + fields = fields.concat(getExpectedFields(searchFields, typeOrTypes)); + } + expectResult(result, expect.objectContaining({ fields })); + } + // also test with no specified type/s const result = getQueryParams({ - mappings, registry, - type: typeOrTypes, + type: undefined, search, searchFields, rootSearchFields, }); let fields = rootSearchFields || []; if (searchFields) { - fields = fields.concat(getExpectedFields(searchFields, typeOrTypes)); + fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES)); } expectResult(result, expect.objectContaining({ fields })); - } - // also test with no specified type/s - const result = getQueryParams({ - mappings, - registry, - type: undefined, - search, - searchFields, - rootSearchFields, + }; + + it('throws an error if a raw search field contains a "." character', () => { + expect(() => + getQueryParams({ + registry, + type: undefined, + search, + searchFields: undefined, + rootSearchFields: ['foo', 'bar.baz'], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"` + ); }); - let fields = rootSearchFields || []; - if (searchFields) { - fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES)); - } - expectResult(result, expect.objectContaining({ fields })); - }; - it('throws an error if a raw search field contains a "." character', () => { - expect(() => - getQueryParams({ - mappings, + it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => { + const result = getQueryParams({ registry, - type: undefined, search, searchFields: undefined, - rootSearchFields: ['foo', 'bar.baz'], - }) - ).toThrowErrorMatchingInlineSnapshot( - `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"` - ); + rootSearchFields: undefined, + }); + expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] })); + }); + + it('includes specified search fields for appropriate type/s', () => { + test({ searchFields: ['title'] }); + }); + + it('supports boosting', () => { + test({ searchFields: ['title^3'] }); + }); + + it('supports multiple search fields', () => { + test({ searchFields: ['title, title.raw'] }); + }); + + it('includes specified raw search fields', () => { + test({ rootSearchFields: ['_id'] }); + }); + + it('supports multiple raw search fields', () => { + test({ rootSearchFields: ['_id', 'originId'] }); + }); + + it('supports search fields and raw search fields', () => { + test({ searchFields: ['title'], rootSearchFields: ['_id'] }); + }); }); - it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => { - const result = getQueryParams({ - mappings, + describe('`defaultSearchOperator` parameter', () => { + it('does not include default_operator when `defaultSearchOperator` is not specified', () => { + const result = getQueryParams({ + registry, + search, + defaultSearchOperator: undefined, + }); + expectResult( + result, + expect.not.objectContaining({ default_operator: expect.anything() }) + ); + }); + + it('includes specified default operator', () => { + const defaultSearchOperator = 'AND'; + const result = getQueryParams({ + registry, + search, + defaultSearchOperator, + }); + expectResult( + result, + expect.objectContaining({ default_operator: defaultSearchOperator }) + ); + }); + }); + }); + + describe('when using prefix search (query.bool.should)', () => { + const searchQuery = 'foo*'; + + const getQueryParamForSearch = ({ + search, + searchFields, + type, + }: { + search?: string; + searchFields?: string[]; + type?: string[]; + }) => + getQueryParams({ registry, search, - searchFields: undefined, - rootSearchFields: undefined, + searchFields, + type, }); - expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] })); - }); - it('includes specified search fields for appropriate type/s', () => { - test({ searchFields: ['title'] }); - }); + it('uses a `should` clause instead of `must`', () => { + const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] }); - it('supports boosting', () => { - test({ searchFields: ['title^3'] }); + expect(result.query.bool.must).toBeUndefined(); + expect(result.query.bool.should).toEqual(expect.any(Array)); + expect(result.query.bool.should.length).toBeGreaterThanOrEqual(1); + expect(result.query.bool.minimum_should_match).toBe(1); }); - - it('supports multiple search fields', () => { - test({ searchFields: ['title, title.raw'] }); + it('includes the `simple_query_string` in the `should` clauses', () => { + const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] }); + expect(result.query.bool.should[0]).toEqual({ + simple_query_string: expect.objectContaining({ + query: searchQuery, + }), + }); }); - it('includes specified raw search fields', () => { - test({ rootSearchFields: ['_id'] }); + it('adds a should clause for each `searchFields` / `type` tuple', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title', 'desc'], + type: ['saved', 'pending'], + }); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(5); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['saved.title', 'pending.title', 'saved.desc', 'pending.desc']); }); - it('supports multiple raw search fields', () => { - test({ rootSearchFields: ['_id', 'originId'] }); + it('uses all registered types when `type` is not provided', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title'], + type: undefined, + }); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(5); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['pending.title', 'saved.title', 'shared.title', 'global.title']); }); - it('supports search fields and raw search fields', () => { - test({ searchFields: ['title'], rootSearchFields: ['_id'] }); + it('removes the prefix search wildcard from the query', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title'], + type: ['saved'], + }); + const shouldClauses = result.query.bool.should; + const mppClauses = shouldClauses.slice(1); + + expect(mppClauses[0].match_phrase_prefix['saved.title'].query).toEqual('foo'); }); - }); - describe('`defaultSearchOperator` parameter', () => { - it('does not include default_operator when `defaultSearchOperator` is not specified', () => { - const result = getQueryParams({ - mappings, - registry, - search, - defaultSearchOperator: undefined, + it("defaults to the type's default search field when `searchFields` is not specified", () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: undefined, + type: ['saved', 'global'], }); - expectResult(result, expect.not.objectContaining({ default_operator: expect.anything() })); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(3); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['saved.title', 'global.name']); }); - it('includes specified default operator', () => { - const defaultSearchOperator = 'AND'; - const result = getQueryParams({ - mappings, - registry, - search, - defaultSearchOperator, + it('supports boosting', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title^3', 'description'], + type: ['saved'], }); - expectResult(result, expect.objectContaining({ default_operator: defaultSearchOperator })); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(3); + + const mppClauses = shouldClauses.slice(1); + + expect(mppClauses.map((clause: any) => clause.match_phrase_prefix)).toEqual([ + { 'saved.title': { query: 'foo', boost: 3 } }, + { 'saved.description': { query: 'foo', boost: 1 } }, + ]); }); }); }); @@ -532,7 +682,6 @@ describe('#getQueryParams', () => { it(`throws for ${type} when namespaces is an empty array`, () => { expect(() => getQueryParams({ - mappings, registry, namespaces: [], }) diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 8d4fe13b9bede3..f73777c4f454fc 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -20,7 +20,6 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; -import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; @@ -28,22 +27,17 @@ import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; * Gets the types based on the type. Uses mappings to support * null type (all types), a single type string or an array */ -function getTypes(mappings: IndexMapping, type?: string | string[]) { +function getTypes(registry: ISavedObjectTypeRegistry, type?: string | string[]) { if (!type) { - return Object.keys(getRootPropertiesObjects(mappings)); + return registry.getAllTypes().map((registeredType) => registeredType.name); } - - if (Array.isArray(type)) { - return type; - } - - return [type]; + return Array.isArray(type) ? type : [type]; } /** * Get the field params based on the types, searchFields, and rootSearchFields */ -function getFieldsForTypes( +function getSimpleQueryStringTypeFields( types: string[], searchFields: string[] = [], rootSearchFields: string[] = [] @@ -130,7 +124,6 @@ export interface HasReferenceQueryParams { export type SearchOperator = 'AND' | 'OR'; interface QueryParams { - mappings: IndexMapping; registry: ISavedObjectTypeRegistry; namespaces?: string[]; type?: string | string[]; @@ -188,11 +181,26 @@ export function getClauseForReference(reference: HasReferenceQueryParams) { }; } +// A de-duplicated set of namespaces makes for a more efficient query. +// +// Additionally, we treat the `*` namespace as the `default` namespace. +// In the Default Distribution, the `*` is automatically expanded to include all available namespaces. +// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` +// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, +// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place +// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. +// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 +const normalizeNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize + ? Array.from( + new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) + ) + : undefined; + /** * Get the "query" related keys for the search body */ export function getQueryParams({ - mappings, registry, namespaces, type, @@ -206,7 +214,7 @@ export function getQueryParams({ kueryNode, }: QueryParams) { const types = getTypes( - mappings, + registry, typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type ); @@ -214,28 +222,10 @@ export function getQueryParams({ hasReference = [hasReference]; } - // A de-duplicated set of namespaces makes for a more effecient query. - // - // Additonally, we treat the `*` namespace as the `default` namespace. - // In the Default Distribution, the `*` is automatically expanded to include all available namespaces. - // However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` - // to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, - // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place - // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. - // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 - const normalizeNamespaces = (namespacesToNormalize?: string[]) => - namespacesToNormalize - ? Array.from( - new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) - ) - : undefined; - const bool: any = { filter: [ ...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []), - ...(hasReference && hasReference.length - ? [getReferencesFilter(hasReference, hasReferenceOperator)] - : []), + ...(hasReference?.length ? [getReferencesFilter(hasReference, hasReferenceOperator)] : []), { bool: { should: types.map((shouldType) => { @@ -251,16 +241,133 @@ export function getQueryParams({ }; if (search) { - bool.must = [ - { - simple_query_string: { - query: search, - ...getFieldsForTypes(types, searchFields, rootSearchFields), - ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), - }, - }, - ]; + const useMatchPhrasePrefix = shouldUseMatchPhrasePrefix(search); + const simpleQueryStringClause = getSimpleQueryStringClause({ + search, + types, + searchFields, + rootSearchFields, + defaultSearchOperator, + }); + + if (useMatchPhrasePrefix) { + bool.should = [ + simpleQueryStringClause, + ...getMatchPhrasePrefixClauses({ search, searchFields, types, registry }), + ]; + bool.minimum_should_match = 1; + } else { + bool.must = [simpleQueryStringClause]; + } } return { query: { bool } }; } + +// we only want to add match_phrase_prefix clauses +// if the search is a prefix search +const shouldUseMatchPhrasePrefix = (search: string): boolean => { + return search.trim().endsWith('*'); +}; + +const getMatchPhrasePrefixClauses = ({ + search, + searchFields, + registry, + types, +}: { + search: string; + searchFields?: string[]; + types: string[]; + registry: ISavedObjectTypeRegistry; +}) => { + // need to remove the prefix search operator + const query = search.replace(/[*]$/, ''); + const mppFields = getMatchPhrasePrefixFields({ searchFields, types, registry }); + return mppFields.map(({ field, boost }) => { + return { + match_phrase_prefix: { + [field]: { + query, + boost, + }, + }, + }; + }); +}; + +interface FieldWithBoost { + field: string; + boost?: number; +} + +const getMatchPhrasePrefixFields = ({ + searchFields = [], + types, + registry, +}: { + searchFields?: string[]; + types: string[]; + registry: ISavedObjectTypeRegistry; +}): FieldWithBoost[] => { + const output: FieldWithBoost[] = []; + + searchFields = searchFields.filter((field) => field !== '*'); + let fields: string[]; + if (searchFields.length === 0) { + fields = types.reduce((typeFields, type) => { + const defaultSearchField = registry.getType(type)?.management?.defaultSearchField; + if (defaultSearchField) { + return [...typeFields, `${type}.${defaultSearchField}`]; + } + return typeFields; + }, [] as string[]); + } else { + fields = []; + for (const field of searchFields) { + fields = fields.concat(types.map((type) => `${type}.${field}`)); + } + } + + fields.forEach((rawField) => { + const [field, rawBoost] = rawField.split('^'); + let boost: number = 1; + if (rawBoost) { + try { + boost = parseInt(rawBoost, 10); + } catch (e) { + boost = 1; + } + } + if (isNaN(boost)) { + boost = 1; + } + output.push({ + field, + boost, + }); + }); + return output; +}; + +const getSimpleQueryStringClause = ({ + search, + types, + searchFields, + rootSearchFields, + defaultSearchOperator, +}: { + search: string; + types: string[]; + searchFields?: string[]; + rootSearchFields?: string[]; + defaultSearchOperator?: SearchOperator; +}) => { + return { + simple_query_string: { + query: search, + ...getSimpleQueryStringTypeFields(types, searchFields, rootSearchFields), + ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), + }, + }; +}; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts index a9f26f71a3f2b5..3522ab9ef17363 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts @@ -76,7 +76,6 @@ describe('getSearchDsl', () => { getSearchDsl(mappings, registry, opts); expect(getQueryParams).toHaveBeenCalledTimes(1); expect(getQueryParams).toHaveBeenCalledWith({ - mappings, registry, namespaces: opts.namespaces, type: opts.type, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index d5da82e5617bea..bddecc4d7f6494 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -71,7 +71,6 @@ export function getSearchDsl( return { ...getQueryParams({ - mappings, registry, namespaces, type, diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index c2e36b4a669ffd..e5da46644672bb 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -334,6 +334,70 @@ export default function ({ getService }) { }); }); + describe('searching for special characters', () => { + before(() => esArchiver.load('saved_objects/find_edgecases')); + after(() => esArchiver.unload('saved_objects/find_edgecases')); + + it('can search for objects with dashes', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'my-vis*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + })); + + it('can search with the prefix search character just after a special one', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'my-*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + })); + + it('can search for objects with asterisk', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'some*vi*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['some*visualization']); + })); + + it('can still search tokens by prefix', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'visuali*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql([ + 'my-visualization', + 'some*visualization', + ]); + })); + }); + describe('without kibana index', () => { before( async () => diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json new file mode 100644 index 00000000000000..0c8b35fd3f4994 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json @@ -0,0 +1,93 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:title-with-dash", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "my-visualization", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:title-with-asterisk", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "some*visualization", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:noise-1", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "Just some noise in the dataset", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:noise-2", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "Just some noise in the dataset", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json new file mode 100644 index 00000000000000..e601c43431437c --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json @@ -0,0 +1,267 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "namespace": { + "type": "keyword" + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + } + } +} From 202dec7c24d14ae2e20be97743089e377daf3047 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 9 Nov 2020 14:17:29 +0300 Subject: [PATCH 56/81] Enable send to background in TSVB (#82835) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/vis_type_timeseries/common/types.ts | 3 ++- .../vis_type_timeseries/common/vis_schema.ts | 1 + .../vis_type_timeseries/public/request_handler.js | 4 +++- .../strategies/abstract_search_strategy.test.js | 8 +++++++- .../strategies/abstract_search_strategy.ts | 13 +++++++++---- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 4520069244527b..8973060848b411 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -18,8 +18,9 @@ */ import { TypeOf } from '@kbn/config-schema'; -import { metricsItems, panel, seriesItems } from './vis_schema'; +import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema'; export type SeriesItemsSchema = TypeOf; export type MetricsItemsSchema = TypeOf; export type PanelSchema = TypeOf; +export type VisPayload = TypeOf; diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 40f776050617e5..27f09fb574b0f3 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -273,4 +273,5 @@ export const visPayloadSchema = schema.object({ min: stringRequired, max: stringRequired, }), + sessionId: schema.maybe(schema.string()), }); diff --git a/src/plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.js index e33d0e254f609f..12b7f3d417ef6a 100644 --- a/src/plugins/vis_type_timeseries/public/request_handler.js +++ b/src/plugins/vis_type_timeseries/public/request_handler.js @@ -32,7 +32,8 @@ export const metricsRequestHandler = async ({ const config = getUISettings(); const timezone = getTimezone(config); const uiStateObj = uiState.get(visParams.type, {}); - const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange); + const dataSearch = getDataStart(); + const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange); const scaledDataFormat = config.get('dateFormat:scaled'); const dateFormat = config.get('dateFormat'); @@ -53,6 +54,7 @@ export const metricsRequestHandler = async ({ panels: [visParams], state: uiStateObj, savedObjectId: savedObjectId || 'unsaved', + sessionId: dataSearch.search.session.getSessionId(), }), }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js index 9710f7daf69b65..2c38e883cd69f3 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js @@ -28,6 +28,7 @@ describe('AbstractSearchStrategy', () => { beforeEach(() => { mockedFields = {}; req = { + payload: {}, pre: { indexPatternsService: { getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields), @@ -60,6 +61,9 @@ describe('AbstractSearchStrategy', () => { const responses = await abstractSearchStrategy.search( { + payload: { + sessionId: 1, + }, requestContext: { search: { search: searchFn }, }, @@ -76,7 +80,9 @@ describe('AbstractSearchStrategy', () => { }, indexType: undefined, }, - {} + { + sessionId: 1, + } ); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index eb22fcb1dd689d..b1e21edf8b588d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -23,8 +23,10 @@ import { IUiSettingsClient, SavedObjectsClientContract, } from 'kibana/server'; + import { Framework } from '../../../plugin'; import { IndexPatternsFetcher } from '../../../../../data/server'; +import { VisPayload } from '../../../../common/types'; /** * ReqFacade is a regular KibanaRequest object extended with additional service @@ -32,17 +34,17 @@ import { IndexPatternsFetcher } from '../../../../../data/server'; * * This will be replaced by standard KibanaRequest and RequestContext objects in a later version. */ -export type ReqFacade = FakeRequest & { +export interface ReqFacade extends FakeRequest { requestContext: RequestHandlerContext; framework: Framework; - payload: unknown; + payload: T; pre: { indexPatternsService?: IndexPatternsFetcher; }; getUiSettingsService: () => IUiSettingsClient; getSavedObjectsClient: () => SavedObjectsClientContract; getEsShardTimeout: () => Promise; -}; +} export class AbstractSearchStrategy { public indexType?: string; @@ -53,8 +55,10 @@ export class AbstractSearchStrategy { this.additionalParams = additionalParams; } - async search(req: ReqFacade, bodies: any[], options = {}) { + async search(req: ReqFacade, bodies: any[], options = {}) { const requests: any[] = []; + const { sessionId } = req.payload; + bodies.forEach((body) => { requests.push( req.requestContext @@ -67,6 +71,7 @@ export class AbstractSearchStrategy { indexType: this.indexType, }, { + sessionId, ...options, } ) From 6110ef82a3a0537a82738c3f039d850772f24306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 9 Nov 2020 12:43:11 +0100 Subject: [PATCH 57/81] [Logs UI] Fix errors during navigation (#78319) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../containers/logs/log_entries/index.ts | 24 ++++++++---- .../infra/public/utils/use_tracked_promise.ts | 37 ++++++++++++++++--- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index 4c8c610794b2eb..214bb16b242839 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { useEffect, useState, useReducer, useCallback } from 'react'; +import { useMountedState } from 'react-use'; import createContainer from 'constate'; import { pick, throttle } from 'lodash'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; @@ -146,15 +147,20 @@ const useFetchEntriesEffect = ( props: LogEntriesProps ) => { const { services } = useKibanaContextForPlugin(); + const isMounted = useMountedState(); const [prevParams, cachePrevParams] = useState(); const [startedStreaming, setStartedStreaming] = useState(false); + const dispatchIfMounted = useCallback((action) => (isMounted() ? dispatch(action) : undefined), [ + dispatch, + isMounted, + ]); const runFetchNewEntriesRequest = async (overrides: Partial = {}) => { if (!props.startTimestamp || !props.endTimestamp) { return; } - dispatch({ type: Action.FetchingNewEntries }); + dispatchIfMounted({ type: Action.FetchingNewEntries }); try { const commonFetchArgs: LogEntriesBaseRequest = { @@ -175,13 +181,15 @@ const useFetchEntriesEffect = ( }; const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch); - dispatch({ type: Action.ReceiveNewEntries, payload }); + dispatchIfMounted({ type: Action.ReceiveNewEntries, payload }); // Move position to the bottom if it's the first load. // Do it in the next tick to allow the `dispatch` to fire if (!props.timeKey && payload.bottomCursor) { setTimeout(() => { - props.jumpToTargetPosition(payload.bottomCursor!); + if (isMounted()) { + props.jumpToTargetPosition(payload.bottomCursor!); + } }); } else if ( props.timeKey && @@ -192,7 +200,7 @@ const useFetchEntriesEffect = ( props.jumpToTargetPosition(payload.topCursor); } } catch (e) { - dispatch({ type: Action.ErrorOnNewEntries }); + dispatchIfMounted({ type: Action.ErrorOnNewEntries }); } }; @@ -210,7 +218,7 @@ const useFetchEntriesEffect = ( return; } - dispatch({ type: Action.FetchingMoreEntries }); + dispatchIfMounted({ type: Action.FetchingMoreEntries }); try { const commonFetchArgs: LogEntriesBaseRequest = { @@ -232,14 +240,14 @@ const useFetchEntriesEffect = ( const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch); - dispatch({ + dispatchIfMounted({ type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, payload, }); return payload.bottomCursor; } catch (e) { - dispatch({ type: Action.ErrorOnMoreEntries }); + dispatchIfMounted({ type: Action.ErrorOnMoreEntries }); } }; @@ -322,7 +330,7 @@ const useFetchEntriesEffect = ( after: props.endTimestamp > prevParams.endTimestamp, }; - dispatch({ type: Action.ExpandRange, payload: shouldExpand }); + dispatchIfMounted({ type: Action.ExpandRange, payload: shouldExpand }); }; const expandRangeEffectDependencies = [ diff --git a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts index 9951b62fa64a30..42518127f68bf7 100644 --- a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts @@ -6,13 +6,15 @@ /* eslint-disable max-classes-per-file */ -import { DependencyList, useEffect, useMemo, useRef, useState } from 'react'; +import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react'; +import { useMountedState } from 'react-use'; interface UseTrackedPromiseArgs { createPromise: (...args: Arguments) => Promise; onResolve?: (result: Result) => void; onReject?: (value: unknown) => void; cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never'; + triggerOrThrow?: 'always' | 'whenMounted'; } /** @@ -64,6 +66,16 @@ interface UseTrackedPromiseArgs { * The last argument is a normal React hook dependency list that indicates * under which conditions a new reference to the configuration object should be * used. + * + * The `onResolve`, `onReject` and possible uncatched errors are only triggered + * if the underlying component is mounted. To ensure they always trigger (i.e. + * if the promise is called in a `useLayoutEffect`) use the `triggerOrThrow` + * attribute: + * + * 'whenMounted': (default) they are called only if the component is mounted. + * + * 'always': they always call. The consumer is then responsible of ensuring no + * side effects happen if the underlying component is not mounted. */ export const useTrackedPromise = ( { @@ -71,9 +83,20 @@ export const useTrackedPromise = ( onResolve = noOp, onReject = noOp, cancelPreviousOn = 'never', + triggerOrThrow = 'whenMounted', }: UseTrackedPromiseArgs, dependencies: DependencyList ) => { + const isComponentMounted = useMountedState(); + const shouldTriggerOrThrow = useCallback(() => { + switch (triggerOrThrow) { + case 'always': + return true; + case 'whenMounted': + return isComponentMounted(); + } + }, [isComponentMounted, triggerOrThrow]); + /** * If a promise is currently pending, this holds a reference to it and its * cancellation function. @@ -144,7 +167,7 @@ export const useTrackedPromise = ( (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise ); - if (onResolve) { + if (onResolve && shouldTriggerOrThrow()) { onResolve(value); } @@ -173,11 +196,13 @@ export const useTrackedPromise = ( (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise ); - if (onReject) { - onReject(value); - } + if (shouldTriggerOrThrow()) { + if (onReject) { + onReject(value); + } - throw value; + throw value; + } } ), }; From 858befef44d10db1ad388b10e48cad84991c8355 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 9 Nov 2020 13:46:46 +0100 Subject: [PATCH 58/81] [APM] Expose APM event client as part of plugin contract (#82724) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../create_apm_event_client/index.ts | 21 +++++---- .../apm/server/lib/helpers/setup_request.ts | 3 +- x-pack/plugins/apm/server/plugin.ts | 47 +++++++++++++++++-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 2bfd3c94ed34c8..9020cb1b9953a3 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -7,14 +7,16 @@ import { ValuesType } from 'utility-types'; import { APMBaseDoc } from '../../../../../typings/es_schemas/raw/apm_base_doc'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { KibanaRequest } from '../../../../../../../../src/core/server'; +import { + KibanaRequest, + LegacyScopedClusterClient, +} from '../../../../../../../../src/core/server'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { ESSearchRequest, ESSearchResponse, } from '../../../../../typings/elasticsearch'; import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices'; -import { APMRequestHandlerContext } from '../../../../routes/typings'; import { addFilterToExcludeLegacyData } from './add_filter_to_exclude_legacy_data'; import { callClientWithDebug } from '../call_client_with_debug'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; @@ -51,20 +53,23 @@ type TypedSearchResponse< export type APMEventClient = ReturnType; export function createApmEventClient({ - context, + esClient, + debug, request, indices, options: { includeFrozen } = { includeFrozen: false }, }: { - context: APMRequestHandlerContext; + esClient: Pick< + LegacyScopedClusterClient, + 'callAsInternalUser' | 'callAsCurrentUser' + >; + debug: boolean; request: KibanaRequest; indices: ApmIndicesConfig; options: { includeFrozen: boolean; }; }) { - const client = context.core.elasticsearch.legacy.client; - return { search( params: TParams, @@ -77,14 +82,14 @@ export function createApmEventClient({ : withProcessorEventFilter; return callClientWithDebug({ - apiCaller: client.callAsCurrentUser, + apiCaller: esClient.callAsCurrentUser, operationName: 'search', params: { ...withPossibleLegacyDataFilter, ignore_throttled: !includeFrozen, }, request, - debug: context.params.query._debug, + debug, }); }, }; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index 5e75535c678b33..363c4128137e02 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -88,7 +88,8 @@ export async function setupRequest( const coreSetupRequest = { indices, apmEventClient: createApmEventClient({ - context, + esClient: context.core.elasticsearch.legacy.client, + debug: context.params.query._debug, request, indices, options: { includeFrozen }, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index d3341b6c1b1638..44269b17759531 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -10,14 +10,17 @@ import { map, take } from 'rxjs/operators'; import { CoreSetup, CoreStart, + KibanaRequest, Logger, Plugin, PluginInitializerContext, + RequestHandlerContext, } from 'src/core/server'; import { APMConfig, APMXPackConfig, mergeConfigs } from '.'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; +import { UI_SETTINGS } from '../../../../src/plugins/data/common'; import { ActionsPlugin } from '../../actions/server'; import { AlertingPlugin } from '../../alerts/server'; import { CloudSetup } from '../../cloud/server'; @@ -30,6 +33,7 @@ import { TaskManagerSetupContract } from '../../task_manager/server'; import { APM_FEATURE, registerFeaturesUsage } from './feature'; import { registerApmAlerts } from './lib/alerts/register_apm_alerts'; import { createApmTelemetry } from './lib/apm_telemetry'; +import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client'; import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; @@ -42,6 +46,11 @@ import { uiSettings } from './ui_settings'; export interface APMPluginSetup { config$: Observable; getApmIndices: () => ReturnType; + createApmEventClient: (params: { + debug?: boolean; + request: KibanaRequest; + context: RequestHandlerContext; + }) => Promise>; } export class APMPlugin implements Plugin { @@ -141,13 +150,41 @@ export class APMPlugin implements Plugin { }, }); + const boundGetApmIndices = async () => + getApmIndices({ + savedObjectsClient: await getInternalSavedObjectsClient(core), + config: await mergedConfig$.pipe(take(1)).toPromise(), + }); + return { config$: mergedConfig$, - getApmIndices: async () => - getApmIndices({ - savedObjectsClient: await getInternalSavedObjectsClient(core), - config: await mergedConfig$.pipe(take(1)).toPromise(), - }), + getApmIndices: boundGetApmIndices, + createApmEventClient: async ({ + request, + context, + debug, + }: { + debug?: boolean; + request: KibanaRequest; + context: RequestHandlerContext; + }) => { + const [indices, includeFrozen] = await Promise.all([ + boundGetApmIndices(), + context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN), + ]); + + const esClient = context.core.elasticsearch.legacy.client; + + return createApmEventClient({ + debug: debug ?? false, + esClient, + request, + indices, + options: { + includeFrozen, + }, + }); + }, }; } From 3c525d7341ebe683e6ed8827927c5b0c18e97631 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 12:56:56 +0000 Subject: [PATCH 59/81] [Alerting] adds an Run When field in the alert flyout to assign the action to an Action Group (#82472) Adds a `RunsWhen` field to actions in the Alerts Flyout when creating / editing an Alert which allows the user to assign specific actions to a certain Action Groups --- .../public/alert_types/astros.tsx | 6 +- .../server/alert_types/always_firing.ts | 18 +- x-pack/plugins/triggers_actions_ui/README.md | 21 +- .../lib/check_action_type_enabled.scss | 12 +- .../action_form.test.tsx | 89 ++- .../action_connector_form/action_form.tsx | 637 +++++------------- .../action_type_form.tsx | 339 ++++++++++ .../connector_add_inline.tsx | 153 +++++ .../connector_add_modal.test.tsx | 3 +- .../connector_add_modal.tsx | 13 +- .../sections/alert_form/alert_form.tsx | 51 +- .../alert_create_flyout.ts | 57 ++ .../fixtures/plugins/alerts/server/plugin.ts | 1 + 13 files changed, 846 insertions(+), 554 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 73c7dfea1263bb..54f989b93e22f4 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -127,9 +127,9 @@ export const PeopleinSpaceExpression: React.FunctionComponent - errs.map((e) => ( -

+ Object.entries(errors).map(([field, errs]: [string, string[]], fieldIndex) => + errs.map((e, index) => ( +

{field}:`: ${errs}`

)) diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index bb1cb0d97689bf..d02406a23045e6 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -5,25 +5,31 @@ */ import uuid from 'uuid'; -import { range } from 'lodash'; +import { range, random } from 'lodash'; import { AlertType } from '../../../../plugins/alerts/server'; import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; +const ACTION_GROUPS = [ + { id: 'small', name: 'small' }, + { id: 'medium', name: 'medium' }, + { id: 'large', name: 'large' }, +]; + export const alertType: AlertType = { id: 'example.always-firing', name: 'Always firing', - actionGroups: [{ id: 'default', name: 'default' }], - defaultActionGroupId: 'default', + actionGroups: ACTION_GROUPS, + defaultActionGroupId: 'small', async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) { const count = (state.count ?? 0) + 1; range(instances) - .map(() => ({ id: uuid.v4() })) - .forEach((instance: { id: string }) => { + .map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)].id! })) + .forEach((instance: { id: string; tshirtSize: string }) => { services .alertInstanceFactory(instance.id) .replaceState({ triggerdOnCycle: count }) - .scheduleActions('default'); + .scheduleActions(instance.tshirtSize); }); return { diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index aabb9899cb3434..32e157255c0cc2 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -1319,19 +1319,19 @@ ActionForm Props definition: interface ActionAccordionFormProps { actions: AlertAction[]; defaultActionGroupId: string; + actionGroups?: ActionGroup[]; setActionIdByIndex: (id: string, index: number) => void; + setActionGroupIdByIndex?: (group: string, index: number) => void; setAlertProperty: (actions: AlertAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; - actionTypeRegistry: TypeRegistry; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + actionTypeRegistry: ActionTypeRegistryContract; + toastNotifications: ToastsSetup; + docLinks: DocLinksStart; actionTypes?: ActionType[]; messageVariables?: ActionVariable[]; defaultActionMessage?: string; - consumer: string; + capabilities: ApplicationStart['capabilities']; } ``` @@ -1339,17 +1339,20 @@ interface ActionAccordionFormProps { |Property|Description| |---|---| |actions|List of actions comes from alert.actions property.| -|defaultActionGroupId|Default action group id to which each new action will belong to.| +|defaultActionGroupId|Default action group id to which each new action will belong by default.| +|actionGroups|Optional. List of action groups to which new action can be assigned. The RunWhen field is only displayed when these action groups are specified| |setActionIdByIndex|Function for changing action 'id' by the proper index in alert.actions array.| +|setActionGroupIdByIndex|Function for changing action 'group' by the proper index in alert.actions array.| |setAlertProperty|Function for changing alert property 'actions'. Used when deleting action from the array to reset it.| |setActionParamsProperty|Function for changing action key/value property by index in alert.actions array.| |http|HttpSetup needed for executing API calls.| |actionTypeRegistry|Registry for action types.| -|toastNotifications|Toast messages.| +|toastNotifications|Toast messages Plugin Setup Contract.| +|docLinks|Documentation links Plugin Start Contract.| |actionTypes|Optional property, which allowes to define a list of available actions specific for a current plugin.| |actionTypes|Optional property, which allowes to define a list of variables for action 'message' property.| |defaultActionMessage|Optional property, which allowes to define a message value for action with 'message' property.| -|consumer|Name of the plugin that creates an action.| +|capabilities|Kibana core's Capabilities ApplicationStart['capabilities'].| AlertsContextProvider value options: diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss index 24dbb865742d8d..bb622829e997ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss @@ -3,9 +3,15 @@ } .actAccordionActionForm { - .euiCard { - box-shadow: none; - } + background-color: $euiColorLightestShade; +} + +.actAccordionActionForm .euiCard { + box-shadow: none; +} + +.actAccordionActionForm__button { + padding: $euiSizeM; } .actConnectorsListGrid { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 7c718e8248e41c..94452e70e6bfa3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -6,7 +6,6 @@ import React, { Fragment, lazy } from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, Alert, AlertAction } from '../../../types'; @@ -112,8 +111,6 @@ describe('action_form', () => { }; describe('action_form in alert', () => { - let wrapper: ReactWrapper; - async function setup(customActions?: AlertAction[]) { const { loadAllActions } = jest.requireMock('../../lib/action_connector_api'); loadAllActions.mockResolvedValueOnce([ @@ -217,7 +214,7 @@ describe('action_form', () => { mutedInstanceIds: [], } as unknown) as Alert; - wrapper = mountWithIntl( + const wrapper = mountWithIntl( { setActionIdByIndex={(id: string, index: number) => { initialAlert.actions[index].id = id; }} + actionGroups={[{ id: 'default', name: 'Default' }]} + setActionGroupIdByIndex={(group: string, index: number) => { + initialAlert.actions[index].group = group; + }} setAlertProperty={(_updatedActions: AlertAction[]) => {}} setActionParamsProperty={(key: string, value: any, index: number) => (initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value }) @@ -297,10 +298,12 @@ describe('action_form', () => { await nextTick(); wrapper.update(); }); + + return wrapper; } it('renders available action cards', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); @@ -314,7 +317,7 @@ describe('action_form', () => { }); it('does not render action types disabled by config', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( '[data-test-subj="disabled-by-config-ActionTypeSelectOption"]' ); @@ -322,52 +325,72 @@ describe('action_form', () => { }); it('render action types which is preconfigured only (disabled by config and with preconfigured connectors)', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); expect(actionOption.exists()).toBeTruthy(); }); + it('renders available action groups for the selected action type', async () => { + const wrapper = await setup(); + const actionOption = wrapper.find( + `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` + ); + actionOption.first().simulate('click'); + const actionGroupsSelect = wrapper.find( + `[data-test-subj="addNewActionConnectorActionGroup-0"]` + ); + expect((actionGroupsSelect.first().props() as any).options).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "addNewActionConnectorActionGroup-0-option-default", + "inputDisplay": "Default", + "value": "default", + }, + ] + `); + }); + it('renders available connectors for the selected action type', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); actionOption.first().simulate('click'); const combobox = wrapper.find(`[data-test-subj="selectActionConnector-${actionType.id}"]`); expect((combobox.first().props() as any).options).toMatchInlineSnapshot(` - Array [ - Object { - "id": "test", - "key": "test", - "label": "Test connector ", - }, - Object { - "id": "test2", - "key": "test2", - "label": "Test connector 2 (preconfigured)", - }, - ] - `); + Array [ + Object { + "id": "test", + "key": "test", + "label": "Test connector ", + }, + Object { + "id": "test2", + "key": "test2", + "label": "Test connector 2 (preconfigured)", + }, + ] + `); }); it('renders only preconfigured connectors for the selected preconfigured action type', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); actionOption.first().simulate('click'); const combobox = wrapper.find('[data-test-subj="selectActionConnector-preconfigured"]'); expect((combobox.first().props() as any).options).toMatchInlineSnapshot(` - Array [ - Object { - "id": "test3", - "key": "test3", - "label": "Preconfigured Only (preconfigured)", - }, - ] - `); + Array [ + Object { + "id": "test3", + "key": "test3", + "label": "Preconfigured Only (preconfigured)", + }, + ] + `); }); it('does not render "Add connector" button for preconfigured only action type', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); actionOption.first().simulate('click'); const preconfigPannel = wrapper.find('[data-test-subj="alertActionAccordion-default"]'); @@ -378,7 +401,7 @@ describe('action_form', () => { }); it('renders action types disabled by license', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( '[data-test-subj="disabled-by-license-ActionTypeSelectOption"]' ); @@ -391,7 +414,7 @@ describe('action_form', () => { }); it(`shouldn't render action types without params component`, async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionTypeWithoutParams.id}-ActionTypeSelectOption"]` ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 74432157f56595..3a7341afe3e079 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Suspense, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -14,25 +14,13 @@ import { EuiIcon, EuiTitle, EuiSpacer, - EuiFormRow, - EuiComboBox, EuiKeyPadMenuItem, - EuiAccordion, - EuiButtonIcon, - EuiEmptyPrompt, - EuiButtonEmpty, EuiToolTip, - EuiIconTip, EuiLink, - EuiCallOut, - EuiHorizontalRule, - EuiText, - EuiLoadingSpinner, } from '@elastic/eui'; import { HttpSetup, ToastsSetup, ApplicationStart, DocLinksStart } from 'kibana/public'; import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; import { - IErrorObject, ActionTypeModel, ActionTypeRegistryContract, AlertAction, @@ -43,15 +31,19 @@ import { } from '../../../types'; import { SectionLoading } from '../../components/section_loading'; import { ConnectorAddModal } from './connector_add_modal'; +import { ActionTypeForm, ActionTypeFormProps } from './action_type_form'; +import { AddConnectorInline } from './connector_add_inline'; import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants'; -import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionGroup } from '../../../../../alerts/common'; -interface ActionAccordionFormProps { +export interface ActionAccordionFormProps { actions: AlertAction[]; defaultActionGroupId: string; + actionGroups?: ActionGroup[]; setActionIdByIndex: (id: string, index: number) => void; + setActionGroupIdByIndex?: (group: string, index: number) => void; setAlertProperty: (actions: AlertAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; @@ -74,7 +66,9 @@ interface ActiveActionConnectorState { export const ActionForm = ({ actions, defaultActionGroupId, + actionGroups, setActionIdByIndex, + setActionGroupIdByIndex, setAlertProperty, setActionParamsProperty, http, @@ -88,8 +82,6 @@ export const ActionForm = ({ capabilities, docLinks, }: ActionAccordionFormProps) => { - const canSave = hasSaveActionsCapability(capabilities); - const [addModalVisible, setAddModalVisibility] = useState(false); const [activeActionItem, setActiveActionItem] = useState( undefined @@ -101,6 +93,10 @@ export const ActionForm = ({ const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [emptyActionsIds, setEmptyActionsIds] = useState([]); + const closeAddConnectorModal = useCallback(() => setAddModalVisibility(false), [ + setAddModalVisibility, + ]); + // load action types useEffect(() => { (async () => { @@ -183,359 +179,6 @@ export const ActionForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actions, connectors]); - const preconfiguredMessage = i18n.translate( - 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', - { - defaultMessage: '(preconfigured)', - } - ); - - const getSelectedOptions = (actionItemId: string) => { - const selectedConnector = connectors.find((connector) => connector.id === actionItemId); - if ( - !selectedConnector || - // if selected connector is not preconfigured and action type is for preconfiguration only, - // do not show regular connectors of this type - (actionTypesIndex && - !actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig && - !selectedConnector.isPreconfigured) - ) { - return []; - } - const optionTitle = `${selectedConnector.name} ${ - selectedConnector.isPreconfigured ? preconfiguredMessage : '' - }`; - return [ - { - label: optionTitle, - value: optionTitle, - id: actionItemId, - 'data-test-subj': 'itemActionConnector', - }, - ]; - }; - - const getActionTypeForm = ( - actionItem: AlertAction, - actionConnector: ActionConnector, - actionParamsErrors: { - errors: IErrorObject; - }, - index: number - ) => { - if (!actionTypesIndex) { - return null; - } - - const actionType = actionTypesIndex[actionItem.actionTypeId]; - - const optionsList = connectors - .filter( - (connectorItem) => - connectorItem.actionTypeId === actionItem.actionTypeId && - // include only enabled by config connectors or preconfigured - (actionType.enabledInConfig || connectorItem.isPreconfigured) - ) - .map(({ name, id, isPreconfigured }) => ({ - label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`, - key: id, - id, - })); - const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); - if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; - const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; - const checkEnabledResult = checkActionFormActionTypeEnabled( - actionTypesIndex[actionConnector.actionTypeId], - connectors.filter((connector) => connector.isPreconfigured) - ); - - const accordionContent = checkEnabledResult.isEnabled ? ( - - - - - } - labelAppend={ - canSave && - actionTypesIndex && - actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? ( - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - - ) : null - } - > - { - setActionIdByIndex(selectedOptions[0].id ?? '', index); - }} - isClearable={false} - /> - - - - - {ParamsFieldsComponent ? ( - - - - -
- } - > - - - ) : null} - - ) : ( - checkEnabledResult.messageCard - ); - - return ( - - - - - - - -
- - - - - - {checkEnabledResult.isEnabled === false && ( - - - - )} - - -
-
-
-
- } - extraAction={ - { - const updatedActions = actions.filter( - (_item: AlertAction, i: number) => i !== index - ); - setAlertProperty(updatedActions); - setIsAddActionPanelOpen( - updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === - 0 - ); - setActiveActionItem(undefined); - }} - /> - } - paddingSize="l" - > - {accordionContent} - - - - ); - }; - - const getAddConnectorsForm = (actionItem: AlertAction, index: number) => { - const actionTypeName = actionTypesIndex - ? actionTypesIndex[actionItem.actionTypeId].name - : actionItem.actionTypeId; - const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); - if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; - - const noConnectorsLabel = ( - - ); - return ( - - - - - - - -
- -
-
-
-
- } - extraAction={ - { - const updatedActions = actions.filter( - (_item: AlertAction, i: number) => i !== index - ); - setAlertProperty(updatedActions); - setIsAddActionPanelOpen( - updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === - 0 - ); - setActiveActionItem(undefined); - }} - /> - } - paddingSize="l" - > - {canSave ? ( - actionItem.id === emptyId) ? ( - noConnectorsLabel - ) : ( - - ) - } - actions={[ - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - , - ]} - /> - ) : ( - -

- -

-
- )} - - - - ); - }; - function addActionType(actionTypeModel: ActionTypeModel) { if (!defaultActionGroupId) { toastNotifications!.addDanger({ @@ -628,116 +271,172 @@ export const ActionForm = ({ }); } - const alertActionsList = actions.map((actionItem: AlertAction, index: number) => { - const actionConnector = connectors.find((field) => field.id === actionItem.id); - // connectors doesn't exists - if (!actionConnector) { - return getAddConnectorsForm(actionItem, index); - } - - const actionErrors: { errors: IErrorObject } = actionTypeRegistry - .get(actionItem.actionTypeId) - ?.validateParams(actionItem.params); - - return getActionTypeForm(actionItem, actionConnector, actionErrors, index); - }); - - return ( + return isLoadingConnectors ? ( + + + + ) : ( - {isLoadingConnectors ? ( - + +

- - ) : ( - - -

- + + + {actionTypesIndex && + actions.map((actionItem: AlertAction, index: number) => { + const actionConnector = connectors.find((field) => field.id === actionItem.id); + // connectors doesn't exists + if (!actionConnector) { + return ( + { + const updatedActions = actions.filter( + (_item: AlertAction, i: number) => i !== index + ); + setAlertProperty(updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id) + .length === 0 + ); + setActiveActionItem(undefined); + }} + onAddConnector={() => { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} /> -

-
- - {alertActionsList} - {isAddActionPanelOpen === false ? ( -
- - - - setIsAddActionPanelOpen(true)} - > - - - - -
- ) : null} - {isAddActionPanelOpen ? ( - - - - -
+ ); + } + + const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + + return ( + { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} + onConnectorSelected={(id: string) => { + setActionIdByIndex(id, index); + }} + onDeleteAction={() => { + const updatedActions = actions.filter( + (_item: AlertAction, i: number) => i !== index + ); + setAlertProperty(updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === + 0 + ); + setActiveActionItem(undefined); + }} + /> + ); + })} + + {isAddActionPanelOpen ? ( + + + + +
+ +
+
+
+ {hasDisabledByLicenseActionTypes && ( + + +
+ -
-
-
- {hasDisabledByLicenseActionTypes && ( - - -
- - - -
-
-
- )} -
- - - {isLoadingActionTypes ? ( - - - - ) : ( - actionTypeNodes - )} - -
- ) : null} + +
+
+
+ )} +
+ + + {isLoadingActionTypes ? ( + + + + ) : ( + actionTypeNodes + )} +
+ ) : ( + + + setIsAddActionPanelOpen(true)} + > + + + + )} - {actionTypesIndex && activeActionItem ? ( + {actionTypesIndex && activeActionItem && addModalVisible ? ( { connectors.push(savedAction); setActionIdByIndex(savedAction.id, activeActionItem.index); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx new file mode 100644 index 00000000000000..38468283b9c197 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -0,0 +1,339 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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, Suspense, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiFormRow, + EuiComboBox, + EuiAccordion, + EuiButtonIcon, + EuiButtonEmpty, + EuiIconTip, + EuiText, + EuiFormLabel, + EuiFormControlLayout, + EuiSuperSelect, + EuiLoadingSpinner, + EuiBadge, +} from '@elastic/eui'; +import { IErrorObject, AlertAction, ActionTypeIndex, ActionConnector } from '../../../types'; +import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionAccordionFormProps } from './action_form'; + +export type ActionTypeFormProps = { + actionItem: AlertAction; + actionConnector: ActionConnector; + actionParamsErrors: { + errors: IErrorObject; + }; + index: number; + onAddConnector: () => void; + onConnectorSelected: (id: string) => void; + onDeleteAction: () => void; + setActionParamsProperty: (key: string, value: any, index: number) => void; + actionTypesIndex: ActionTypeIndex; + connectors: ActionConnector[]; +} & Pick< + ActionAccordionFormProps, + | 'defaultActionGroupId' + | 'actionGroups' + | 'setActionGroupIdByIndex' + | 'setActionParamsProperty' + | 'http' + | 'actionTypeRegistry' + | 'toastNotifications' + | 'docLinks' + | 'messageVariables' + | 'defaultActionMessage' + | 'capabilities' +>; + +const preconfiguredMessage = i18n.translate( + 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', + { + defaultMessage: '(preconfigured)', + } +); + +export const ActionTypeForm = ({ + actionItem, + actionConnector, + actionParamsErrors, + index, + onAddConnector, + onConnectorSelected, + onDeleteAction, + setActionParamsProperty, + actionTypesIndex, + connectors, + http, + toastNotifications, + docLinks, + capabilities, + actionTypeRegistry, + defaultActionGroupId, + defaultActionMessage, + messageVariables, + actionGroups, + setActionGroupIdByIndex, +}: ActionTypeFormProps) => { + const [isOpen, setIsOpen] = useState(true); + + const canSave = hasSaveActionsCapability(capabilities); + const getSelectedOptions = (actionItemId: string) => { + const selectedConnector = connectors.find((connector) => connector.id === actionItemId); + if ( + !selectedConnector || + // if selected connector is not preconfigured and action type is for preconfiguration only, + // do not show regular connectors of this type + (actionTypesIndex && + !actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig && + !selectedConnector.isPreconfigured) + ) { + return []; + } + const optionTitle = `${selectedConnector.name} ${ + selectedConnector.isPreconfigured ? preconfiguredMessage : '' + }`; + return [ + { + label: optionTitle, + value: optionTitle, + id: actionItemId, + 'data-test-subj': 'itemActionConnector', + }, + ]; + }; + + const actionType = actionTypesIndex[actionItem.actionTypeId]; + + const optionsList = connectors + .filter( + (connectorItem) => + connectorItem.actionTypeId === actionItem.actionTypeId && + // include only enabled by config connectors or preconfigured + (actionType.enabledInConfig || connectorItem.isPreconfigured) + ) + .map(({ name, id, isPreconfigured }) => ({ + label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`, + key: id, + id, + })); + const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); + if (!actionTypeRegistered) return null; + + const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionTypesIndex[actionConnector.actionTypeId], + connectors.filter((connector) => connector.isPreconfigured) + ); + + const defaultActionGroup = actionGroups?.find(({ id }) => id === defaultActionGroupId); + const selectedActionGroup = + actionGroups?.find(({ id }) => id === actionItem.group) ?? defaultActionGroup; + + const accordionContent = checkEnabledResult.isEnabled ? ( + + {actionGroups && selectedActionGroup && setActionGroupIdByIndex && ( + + + + + + + } + > + ({ + value, + inputDisplay: name, + 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, + }))} + valueOfSelected={selectedActionGroup.id} + onChange={(group) => { + setActionGroupIdByIndex(group, index); + }} + /> + + + + + + )} + + + + } + labelAppend={ + canSave && + actionTypesIndex && + actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? ( + + + + ) : null + } + > + { + onConnectorSelected(selectedOptions[0].id ?? ''); + }} + isClearable={false} + /> + + + + + {ParamsFieldsComponent ? ( + + + + + + } + > + + + ) : null} + + ) : ( + checkEnabledResult.messageCard + ); + + return ( + + + + + + + +
+ + + + + {selectedActionGroup && !isOpen && ( + + {selectedActionGroup.name} + + )} + + {checkEnabledResult.isEnabled === false && ( + + + + )} + + +
+
+
+ + } + extraAction={ + + } + > + {accordionContent} +
+ +
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx new file mode 100644 index 00000000000000..97baf4a36cb4cc --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiAccordion, + EuiButtonIcon, + EuiEmptyPrompt, + EuiCallOut, + EuiText, +} from '@elastic/eui'; +import { AlertAction, ActionTypeIndex } from '../../../types'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionAccordionFormProps } from './action_form'; + +type AddConnectorInFormProps = { + actionTypesIndex: ActionTypeIndex; + actionItem: AlertAction; + index: number; + onAddConnector: () => void; + onDeleteConnector: () => void; + emptyActionsIds: string[]; +} & Pick; + +export const AddConnectorInline = ({ + actionTypesIndex, + actionItem, + index, + onAddConnector, + onDeleteConnector, + actionTypeRegistry, + emptyActionsIds, + defaultActionGroupId, + capabilities, +}: AddConnectorInFormProps) => { + const canSave = hasSaveActionsCapability(capabilities); + + const actionTypeName = actionTypesIndex + ? actionTypesIndex[actionItem.actionTypeId].name + : actionItem.actionTypeId; + const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); + if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; + + const noConnectorsLabel = ( + + ); + return ( + + + + + + + +
+ +
+
+
+ + } + extraAction={ + + } + paddingSize="l" + > + {canSave ? ( + actionItem.id === emptyId) ? ( + noConnectorsLabel + ) : ( + + ) + } + actions={[ + + + , + ]} + /> + ) : ( + +

+ +

+
+ )} +
+ +
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx index cba9eea3cf3f7f..71a3936ed50559 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -65,8 +65,7 @@ describe('connector_add_modal', () => { const wrapper = mountWithIntl( {}} + onClose={() => {}} actionType={actionType} http={deps!.http} actionTypeRegistry={deps!.actionTypeRegistry} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 13ec8395aa5578..de27256bf566cb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -32,8 +32,7 @@ import { interface ConnectorAddModalProps { actionType: ActionType; - addModalVisible: boolean; - setAddModalVisibility: React.Dispatch>; + onClose: () => void; postSaveEventHandler?: (savedAction: ActionConnector) => void; http: HttpSetup; actionTypeRegistry: ActionTypeRegistryContract; @@ -48,8 +47,7 @@ interface ConnectorAddModalProps { export const ConnectorAddModal = ({ actionType, - addModalVisible, - setAddModalVisibility, + onClose, postSaveEventHandler, http, toastNotifications, @@ -79,14 +77,11 @@ export const ConnectorAddModal = ({ >(undefined); const closeModal = useCallback(() => { - setAddModalVisibility(false); setConnector(initialConnector); setServerError(undefined); - }, [initialConnector, setAddModalVisibility]); + onClose(); + }, [initialConnector, onClose]); - if (!addModalVisible) { - return null; - } const actionTypeModel = actionTypeRegistry.get(actionType.id); const errors = { ...actionTypeModel?.validateConnector(connector).errors, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 9a637ea750f815..20ad9a8d7c7014 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useState, useEffect, Suspense } from 'react'; +import React, { Fragment, useState, useEffect, Suspense, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -153,9 +153,17 @@ export const AlertForm = ({ setAlertTypeModel(alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null); }, [alert, alertTypeRegistry]); - const setAlertProperty = (key: string, value: any) => { - dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); - }; + const setAlertProperty = useCallback( + (key: string, value: any) => { + dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); + }, + [dispatch] + ); + + const setActions = useCallback( + (updatedActions: AlertAction[]) => setAlertProperty('actions', updatedActions), + [setAlertProperty] + ); const setAlertParams = (key: string, value: any) => { dispatch({ command: { type: 'setAlertParams' }, payload: { key, value } }); @@ -169,9 +177,12 @@ export const AlertForm = ({ dispatch({ command: { type: 'setAlertActionProperty' }, payload: { key, value, index } }); }; - const setActionParamsProperty = (key: string, value: any, index: number) => { - dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); - }; + const setActionParamsProperty = useCallback( + (key: string, value: any, index: number) => { + dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); + }, + [dispatch] + ); const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; @@ -202,6 +213,7 @@ export const AlertForm = ({ label={item.name} onClick={() => { setAlertProperty('alertTypeId', item.id); + setActions([]); setAlertTypeModel(item); setAlertProperty('params', {}); if (alertTypesIndex && alertTypesIndex.has(item.id)) { @@ -289,26 +301,25 @@ export const AlertForm = ({ /> ) : null} - {canShowActions && defaultActionGroupId ? ( + {canShowActions && + defaultActionGroupId && + alertTypeModel && + alertTypesIndex?.has(alert.alertTypeId) ? ( - a.name.toUpperCase().localeCompare(b.name.toUpperCase()) - ) - : undefined - } + messageVariables={actionVariablesFromAlertType( + alertTypesIndex.get(alert.alertTypeId)! + ).sort((a, b) => a.name.toUpperCase().localeCompare(b.name.toUpperCase()))} defaultActionGroupId={defaultActionGroupId} + actionGroups={alertTypesIndex.get(alert.alertTypeId)!.actionGroups} setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)} - setAlertProperty={(updatedActions: AlertAction[]) => - setAlertProperty('actions', updatedActions) - } - setActionParamsProperty={(key: string, value: any, index: number) => - setActionParamsProperty(key, value, index) + setActionGroupIdByIndex={(group: string, index: number) => + setActionProperty('group', group, index) } + setAlertProperty={setActions} + setActionParamsProperty={setActionParamsProperty} http={http} actionTypeRegistry={actionTypeRegistry} defaultActionMessage={alertTypeModel?.defaultActionMessage} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 7d99d3635106dd..ee0de582a9bffa 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -55,6 +55,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.click(); } + async function defineAlwaysFiringAlert(alertName: string) { + await pageObjects.triggersActionsUI.clickCreateAlertButton(); + await testSubjects.setValue('alertNameInput', alertName); + await testSubjects.click('test.always-firing-SelectOption'); + } + describe('create alert', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); @@ -106,6 +112,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); }); + it('should create an alert with actions in multiple groups', async () => { + const alertName = generateUniqueKey(); + await defineAlwaysFiringAlert(alertName); + + // create Slack connector and attach an action using it + await testSubjects.click('.slack-ActionTypeSelectOption'); + await testSubjects.click('addNewActionConnectorButton-.slack'); + const slackConnectorName = generateUniqueKey(); + await testSubjects.setValue('nameInput', slackConnectorName); + await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + const createdConnectorToastTitle = await pageObjects.common.closeToast(); + expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + await testSubjects.setValue('messageTextArea', 'test message '); + await ( + await find.byCssSelector( + '[data-test-subj="alertActionAccordion-0"] [data-test-subj="messageTextArea"]' + ) + ).type('some text '); + + await testSubjects.click('addAlertActionButton'); + await testSubjects.click('.slack-ActionTypeSelectOption'); + await testSubjects.setValue('messageTextArea', 'test message '); + await ( + await find.byCssSelector( + '[data-test-subj="alertActionAccordion-1"] [data-test-subj="messageTextArea"]' + ) + ).type('some text '); + + await testSubjects.click('addNewActionConnectorActionGroup-1'); + await testSubjects.click('addNewActionConnectorActionGroup-1-option-other'); + + await testSubjects.click('saveAlertButton'); + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Created alert "${alertName}"`); + await pageObjects.triggersActionsUI.searchAlerts(alertName); + const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResultsAfterSave).to.eql([ + { + name: alertName, + tagsText: '', + alertType: 'Always Firing', + interval: '1m', + }, + ]); + + // clean up created alert + const alertsToDelete = await getAlertsByName(alertName); + await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); + }); + it('should show save confirmation before creating alert with no actions', async () => { const alertName = generateUniqueKey(); await defineAlert(alertName); diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts index e3927f6bfffb95..6f9d0103786244 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts @@ -78,6 +78,7 @@ function createAlwaysFiringAlertType(alerts: AlertingSetup) { { id: 'default', name: 'Default' }, { id: 'other', name: 'Other' }, ], + defaultActionGroupId: 'default', producer: 'alerts', async executor(alertExecutorOptions: any) { const { services, state, params } = alertExecutorOptions; From d1ef0d6704237cade5ff6a4246e42148dadb0b9e Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 9 Nov 2020 13:11:51 +0000 Subject: [PATCH 60/81] skip flaky suite (#57426) --- .../functional_with_es_ssl/apps/triggers_actions_ui/details.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 9e4006681dc8dd..1d86d95b7a796d 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -306,7 +306,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('Alert Instances', function () { + // FLAKY: https://github.com/elastic/kibana/issues/57426 + describe.skip('Alert Instances', function () { const testRunUuid = uuid.v4(); let alert: any; From f2f76e104af5d0515773c173475efda7d80c1d31 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 9 Nov 2020 14:29:53 +0100 Subject: [PATCH 61/81] [ILM] Migrate Delete phase and name field to Form Lib (#82834) * remove use of legacy state system and legacy serialization * remove legacy min_age input component and re-add missing import * rename shared -> shared_fields for more clarity * some more cleanup and fixing regressions on policy name for creating new policy from existing policy * move extract policy static code to lib folder and remove "policies" dir from services * fix jest tests and minor policy flyout inconsistency * remove legacy helper * fix client integration tests * fix min for set index priority * moved save policy function into edit policy section * remove unused translations * refactor form files to own edit_policy/form folder * remove "fix errors" badge to fix UX - users can see errors in line before pressing save so the value of this badge has diminished * fix i18n after removing phase error badge Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../edit_policy/edit_policy.helpers.tsx | 5 + .../edit_policy/edit_policy.test.ts | 3 +- .../__jest__/components/edit_policy.test.tsx | 242 ++++---- .../public/application/lib/policies.ts | 32 ++ .../sections/edit_policy/components/index.ts | 3 - .../components/min_age_input_legacy.tsx | 263 --------- .../components/phase_error_message.tsx | 19 - .../phases/cold_phase/cold_phase.tsx | 23 +- .../{ => delete_phase}/delete_phase.tsx | 94 +-- .../components/phases/delete_phase/index.ts | 7 + .../components/phases/hot_phase/hot_phase.tsx | 12 +- .../components/cloud_data_tier_callout.tsx | 0 .../components/data_tier_allocation.scss | 0 .../components/data_tier_allocation.tsx | 0 .../components/default_allocation_notice.tsx | 0 .../components/index.ts | 0 .../components/no_node_attributes_warning.tsx | 0 .../components/node_allocation.tsx | 9 +- .../components/node_attrs_details.tsx | 0 .../components/node_data_provider.tsx | 0 .../components/types.ts | 0 .../data_tier_allocation_field.tsx | 0 .../data_tier_allocation_field/index.ts | 0 .../forcemerge_field.tsx | 6 +- .../phases/{shared => shared_fields}/index.ts | 2 + .../min_age_input_field/index.ts | 0 .../min_age_input_field.tsx | 0 .../min_age_input_field/util.ts | 0 .../set_priority_input.tsx | 9 +- .../snapshot_policies_field.tsx} | 113 ++-- .../phases/warm_phase/warm_phase.tsx | 27 +- .../components/policy_json_flyout.tsx | 22 +- .../edit_policy/edit_policy.container.tsx | 23 +- .../sections/edit_policy/edit_policy.tsx | 537 ++++++++---------- .../edit_policy/edit_policy_context.tsx | 12 +- .../edit_policy/{ => form}/deserializer.ts | 21 +- .../sections/edit_policy/form/index.ts | 13 + .../{form_schema.ts => form/schema.ts} | 53 +- .../edit_policy/{ => form}/serializer.ts | 24 +- .../validations.ts} | 77 ++- .../sections/edit_policy/i18n_texts.ts | 36 ++ .../edit_policy/save_policy.ts} | 15 +- .../application/sections/edit_policy/types.ts | 5 + .../services/policies/delete_phase.ts | 88 --- .../policies/policy_serialization.test.ts | 198 ------- .../services/policies/policy_serialization.ts | 82 --- .../services/policies/policy_validation.ts | 144 ----- .../public/shared_imports.ts | 3 + .../translations/translations/ja-JP.json | 6 - .../translations/translations/zh-CN.json | 6 - 50 files changed, 749 insertions(+), 1485 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{ => delete_phase}/delete_phase.tsx (50%) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/cloud_data_tier_callout.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/data_tier_allocation.scss (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/data_tier_allocation.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/default_allocation_notice.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/no_node_attributes_warning.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/node_allocation.tsx (90%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/node_attrs_details.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/node_data_provider.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/types.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/data_tier_allocation_field.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/forcemerge_field.tsx (94%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/index.ts (88%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/min_age_input_field/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/min_age_input_field/min_age_input_field.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/min_age_input_field/util.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/set_priority_input.tsx (83%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{snapshot_policies.tsx => phases/shared_fields/snapshot_policies_field.tsx} (68%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{ => form}/deserializer.ts (82%) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{form_schema.ts => form/schema.ts} (90%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{ => form}/serializer.ts (90%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{form_validations.ts => form/validations.ts} (50%) rename x-pack/plugins/index_lifecycle_management/public/application/{services/policies/policy_save.ts => sections/edit_policy/save_policy.ts} (84%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.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 0b9f47e188d152..646978dd68153c 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 @@ -221,6 +221,11 @@ export const setup = async () => { setFreeze, setIndexPriority: setIndexPriority('cold'), }, + delete: { + enable: enable('delete'), + setMinAgeValue: setMinAgeValue('delete'), + setMinAgeUnits: setMinAgeUnits('delete'), + }, }, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 11fadf51f27f8d..4ee67d1ed8a19e 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -367,7 +367,6 @@ describe('', () => { expect(testBed.find('snapshotPolicyCombobox').prop('data-currentvalue')).toEqual([ { label: DELETE_PHASE_POLICY.policy.phases.delete?.actions.wait_for_snapshot?.policy, - value: DELETE_PHASE_POLICY.policy.phases.delete?.actions.wait_for_snapshot?.policy, }, ]); }); @@ -412,7 +411,7 @@ describe('', () => { test('wait for snapshot field should delete action if field is empty', async () => { const { actions } = testBed; - actions.setWaitForSnapshotPolicy(''); + await actions.setWaitForSnapshotPolicy(''); await actions.savePolicy(); const expected = { 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 4a3fedfb264ac1..43910583ceec91 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 @@ -20,27 +20,27 @@ import { notificationServiceMock, fatalErrorsServiceMock, } from '../../../../../src/core/public/mocks'; + import { usageCollectionPluginMock } from '../../../../../src/plugins/usage_collection/public/mocks'; + import { CloudSetup } from '../../../cloud/public'; import { EditPolicy } from '../../public/application/sections/edit_policy/edit_policy'; +import { + EditPolicyContextProvider, + EditPolicyContextValue, +} from '../../public/application/sections/edit_policy/edit_policy_context'; + import { KibanaContextProvider } from '../../public/shared_imports'; + import { init as initHttp } from '../../public/application/services/http'; import { init as initUiMetric } from '../../public/application/services/ui_metric'; import { init as initNotification } from '../../public/application/services/notification'; import { PolicyFromES } from '../../common/types'; -import { - positiveNumberRequiredMessage, - policyNameRequiredMessage, - policyNameStartsWithUnderscoreErrorMessage, - policyNameContainsCommaErrorMessage, - policyNameContainsSpaceErrorMessage, - policyNameMustBeDifferentErrorMessage, - policyNameAlreadyUsedErrorMessage, -} from '../../public/application/services/policies/policy_validation'; import { i18nTexts } from '../../public/application/sections/edit_policy/i18n_texts'; import { editPolicyHelpers } from './helpers'; +import { defaultPolicy } from '../../public/application/constants'; // @ts-ignore initHttp(axios.create({ adapter: axiosXhrAdapter })); @@ -122,14 +122,11 @@ const noRollover = async (rendered: ReactWrapper) => { const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => { return findTestSubject(rendered, `${phase}-selectedNodeAttrs`); }; -const setPolicyName = (rendered: ReactWrapper, policyName: string) => { +const setPolicyName = async (rendered: ReactWrapper, policyName: string) => { const policyNameField = findTestSubject(rendered, 'policyNameField'); - policyNameField.simulate('change', { target: { value: policyName } }); - rendered.update(); -}; -const setPhaseAfterLegacy = (rendered: ReactWrapper, phase: string, after: string | number) => { - const afterInput = rendered.find(`input#${phase}-selectedMinimumAge`); - afterInput.simulate('change', { target: { value: after } }); + await act(async () => { + policyNameField.simulate('change', { target: { value: policyName } }); + }); rendered.update(); }; const setPhaseAfter = async (rendered: ReactWrapper, phase: string, after: string | number) => { @@ -157,6 +154,32 @@ const save = async (rendered: ReactWrapper) => { }); rendered.update(); }; + +const MyComponent = ({ + isCloudEnabled, + isNewPolicy, + policy: _policy, + existingPolicies, + getUrlForApp, + policyName, +}: EditPolicyContextValue & { isCloudEnabled: boolean }) => { + return ( + + + + + + ); +}; + describe('edit policy', () => { beforeAll(() => { jest.useFakeTimers(); @@ -179,14 +202,14 @@ describe('edit policy', () => { beforeEach(() => { component = ( - - - + ); ({ http } = editPolicyHelpers.setup()); @@ -198,62 +221,78 @@ describe('edit policy', () => { test('should show error when trying to save empty form', async () => { const rendered = mountWithIntl(component); await save(rendered); - expectedErrorMessages(rendered, [policyNameRequiredMessage]); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameRequiredMessage]); }); test('should show error when trying to save policy name with space', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'my policy'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameContainsSpaceErrorMessage]); + await setPolicyName(rendered, 'my policy'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); }); test('should show error when trying to save policy name that is already used', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'testy0'); - rendered.update(); - await save(rendered); - expectedErrorMessages(rendered, [policyNameAlreadyUsedErrorMessage]); + await setPolicyName(rendered, 'testy0'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [ + i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, + ]); }); test('should show error when trying to save as new policy but using the same name', async () => { component = ( - ); const rendered = mountWithIntl(component); findTestSubject(rendered, 'saveAsNewSwitch').simulate('click'); rendered.update(); - setPolicyName(rendered, 'testy0'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameMustBeDifferentErrorMessage]); + await setPolicyName(rendered, 'testy0'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [ + i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, + ]); }); test('should show error when trying to save policy name with comma', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'my,policy'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameContainsCommaErrorMessage]); + await setPolicyName(rendered, 'my,policy'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); }); test('should show error when trying to save policy name starting with underscore', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, '_mypolicy'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameStartsWithUnderscoreErrorMessage]); + await setPolicyName(rendered, '_mypolicy'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [ + i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage, + ]); }); test('should show correct json in policy flyout', async () => { - const rendered = mountWithIntl(component); + const rendered = mountWithIntl( + + ); await act(async () => { findTestSubject(rendered, 'requestButton').simulate('click'); }); rendered.update(); + const json = rendered.find(`code`).text(); - const expected = `PUT _ilm/policy/\n${JSON.stringify( + const expected = `PUT _ilm/policy/my-policy\n${JSON.stringify( { policy: { phases: { @@ -282,7 +321,7 @@ describe('edit policy', () => { test('should show errors when trying to save with no max size and no max age', async () => { const rendered = mountWithIntl(component); expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeFalsy(); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '' } }); @@ -298,7 +337,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); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); @@ -309,7 +348,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); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); @@ -319,7 +358,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); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '-1' } }); @@ -329,7 +368,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); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '0' } }); @@ -337,21 +376,21 @@ describe('edit policy', () => { waitForFormLibValidation(rendered); expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); - test('should show forcemerge input when rollover enabled', () => { + test('should show forcemerge input when rollover enabled', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeTruthy(); }); test('should hide forcemerge input when rollover is disabled', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await noRollover(rendered); waitForFormLibValidation(rendered); expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeFalsy(); }); test('should show positive number required above zero error when trying to save hot phase with 0 for force merge', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); act(() => { findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click'); }); @@ -365,7 +404,7 @@ describe('edit policy', () => { }); test('should show positive number above 0 required error when trying to save hot phase with -1 for force merge', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click'); rendered.update(); const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments'); @@ -379,7 +418,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await setPhaseIndexPriority(rendered, 'hot', '-1'); waitForFormLibValidation(rendered); expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); @@ -397,7 +436,7 @@ describe('edit policy', () => { test('should show number required error when trying to save empty warm phase', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', ''); waitForFormLibValidation(rendered); @@ -406,7 +445,7 @@ describe('edit policy', () => { test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '0'); waitForFormLibValidation(rendered); @@ -415,7 +454,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save warm phase with -1 for after', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '-1'); waitForFormLibValidation(rendered); @@ -424,7 +463,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save warm phase with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); await setPhaseAfter(rendered, 'warm', '-1'); @@ -434,7 +473,7 @@ describe('edit policy', () => { test('should show positive number required above zero error when trying to save warm phase with 0 for shrink', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); act(() => { findTestSubject(rendered, 'shrinkSwitch').simulate('click'); @@ -451,7 +490,7 @@ describe('edit policy', () => { test('should show positive number above 0 required error when trying to save warm phase with -1 for shrink', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); act(() => { @@ -468,7 +507,7 @@ describe('edit policy', () => { test('should show positive number required above zero error when trying to save warm phase with 0 for force merge', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); act(() => { @@ -485,7 +524,7 @@ describe('edit policy', () => { test('should show positive number above 0 required error when trying to save warm phase with -1 for force merge', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); await act(async () => { @@ -503,7 +542,7 @@ describe('edit policy', () => { server.respondImmediately = false; const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); @@ -517,7 +556,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); @@ -527,7 +566,7 @@ describe('edit policy', () => { test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); @@ -539,7 +578,7 @@ describe('edit policy', () => { test('should show view node attributes link when attribute selected and show flyout when clicked', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); @@ -568,7 +607,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy(); @@ -581,7 +620,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy(); @@ -594,7 +633,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); @@ -611,7 +650,7 @@ describe('edit policy', () => { test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); await setPhaseAfter(rendered, 'cold', '0'); waitForFormLibValidation(rendered); @@ -621,7 +660,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save cold phase with -1 for after', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); await setPhaseAfter(rendered, 'cold', '-1'); waitForFormLibValidation(rendered); @@ -631,7 +670,7 @@ describe('edit policy', () => { server.respondImmediately = false; const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); @@ -645,7 +684,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); @@ -655,7 +694,7 @@ describe('edit policy', () => { test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); @@ -667,7 +706,7 @@ describe('edit policy', () => { test('should show view node attributes link when attribute selected and show flyout when clicked', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); @@ -689,7 +728,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); await setPhaseAfter(rendered, 'cold', '1'); await setPhaseIndexPriority(rendered, 'cold', '-1'); @@ -704,7 +743,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy(); @@ -717,7 +756,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy(); @@ -730,7 +769,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); @@ -740,20 +779,20 @@ describe('edit policy', () => { test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'delete'); - setPhaseAfterLegacy(rendered, 'delete', '0'); - await save(rendered); + await setPhaseAfter(rendered, 'delete', '0'); + waitForFormLibValidation(rendered); expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save delete phase with -1 for after', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'delete'); - setPhaseAfterLegacy(rendered, 'delete', '-1'); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); + await setPhaseAfter(rendered, 'delete', '-1'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); }); describe('not on cloud', () => { @@ -768,7 +807,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -782,14 +821,13 @@ describe('edit policy', () => { describe('on cloud', () => { beforeEach(() => { component = ( - - - + ); ({ http } = editPolicyHelpers.setup()); ({ server, httpRequestsMockHelpers } = http); @@ -808,7 +846,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -829,7 +867,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -849,7 +887,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'cloudDataTierCallout').exists()).toBeTruthy(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts new file mode 100644 index 00000000000000..c4a91978a37655 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.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 { PolicyFromES } from '../../../common/types'; + +export const splitSizeAndUnits = (field: string): { size: string; units: string } => { + let size = ''; + let units = ''; + + const result = /(\d+)(\w+)/.exec(field); + if (result) { + size = result[1]; + units = result[2]; + } + + return { + size, + units, + }; +}; + +export const getPolicyByName = ( + policies: PolicyFromES[] | null | undefined, + policyName: string = '' +): PolicyFromES | undefined => { + if (policies && policies.length > 0) { + return policies.find((policy: PolicyFromES) => policy.name === policyName); + } +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index a04608338718e3..326f6ff87dc3b8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -7,11 +7,8 @@ export { ActiveBadge } from './active_badge'; export { ErrableFormRow } from './form_errors'; export { LearnMoreLink } from './learn_more_link'; -export { MinAgeInput } from './min_age_input_legacy'; export { OptionalLabel } from './optional_label'; -export { PhaseErrorMessage } from './phase_error_message'; export { PolicyJsonFlyout } from './policy_json_flyout'; -export { SnapshotPolicies } from './snapshot_policies'; export { DescribedFormField } from './described_form_field'; export * from './phases'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx deleted file mode 100644 index 6fcf35b7992896..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; - -import { LearnMoreLink } from './learn_more_link'; -import { ErrableFormRow } from './form_errors'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; -import { PhaseWithMinAge, Phases } from '../../../../../common/types'; - -function getTimingLabelForPhase(phase: keyof Phases) { - // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. - switch (phase) { - case 'warm': - return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel', { - defaultMessage: 'Timing for warm phase', - }); - - case 'cold': - return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel', { - defaultMessage: 'Timing for cold phase', - }); - - case 'delete': - return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel', { - defaultMessage: 'Timing for delete phase', - }); - } -} - -function getUnitsAriaLabelForPhase(phase: keyof Phases) { - // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. - switch (phase) { - case 'warm': - return i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel', - { - defaultMessage: 'Units for timing of warm phase', - } - ); - - case 'cold': - return i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel', - { - defaultMessage: 'Units for timing of cold phase', - } - ); - - case 'delete': - return i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel', - { - defaultMessage: 'Units for timing of delete phase', - } - ); - } -} - -interface Props { - rolloverEnabled: boolean; - errors?: PhaseValidationErrors; - phase: keyof Phases & string; - phaseData: T; - setPhaseData: (dataKey: keyof T & string, value: string) => void; - isShowingErrors: boolean; -} - -export const MinAgeInput = ({ - rolloverEnabled, - errors, - phaseData, - phase, - setPhaseData, - isShowingErrors, -}: React.PropsWithChildren>): React.ReactElement => { - let daysOptionLabel; - let hoursOptionLabel; - let minutesOptionLabel; - let secondsOptionLabel; - let millisecondsOptionLabel; - let microsecondsOptionLabel; - let nanosecondsOptionLabel; - - if (rolloverEnabled) { - daysOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel', - { - defaultMessage: 'days from rollover', - } - ); - - hoursOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel', - { - defaultMessage: 'hours from rollover', - } - ); - minutesOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel', - { - defaultMessage: 'minutes from rollover', - } - ); - - secondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel', - { - defaultMessage: 'seconds from rollover', - } - ); - millisecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel', - { - defaultMessage: 'milliseconds from rollover', - } - ); - - microsecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel', - { - defaultMessage: 'microseconds from rollover', - } - ); - - nanosecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel', - { - defaultMessage: 'nanoseconds from rollover', - } - ); - } else { - daysOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel', - { - defaultMessage: 'days from index creation', - } - ); - - hoursOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel', - { - defaultMessage: 'hours from index creation', - } - ); - - minutesOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel', - { - defaultMessage: 'minutes from index creation', - } - ); - - secondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel', - { - defaultMessage: 'seconds from index creation', - } - ); - - millisecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel', - { - defaultMessage: 'milliseconds from index creation', - } - ); - - microsecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel', - { - defaultMessage: 'microseconds from index creation', - } - ); - - nanosecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel', - { - defaultMessage: 'nanoseconds from index creation', - } - ); - } - - // check that these strings are valid properties - const selectedMinimumAgeProperty = propertyof('selectedMinimumAge'); - const selectedMinimumAgeUnitsProperty = propertyof('selectedMinimumAgeUnits'); - return ( - - - - } - /> - } - > - { - setPhaseData(selectedMinimumAgeProperty, e.target.value); - }} - min={0} - /> - - - - - setPhaseData(selectedMinimumAgeUnitsProperty, e.target.value)} - options={[ - { - value: 'd', - text: daysOptionLabel, - }, - { - value: 'h', - text: hoursOptionLabel, - }, - { - value: 'm', - text: minutesOptionLabel, - }, - { - value: 's', - text: secondsOptionLabel, - }, - { - value: 'ms', - text: millisecondsOptionLabel, - }, - { - value: 'micros', - text: microsecondsOptionLabel, - }, - { - value: 'nanos', - text: nanosecondsOptionLabel, - }, - ]} - /> - - - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx deleted file mode 100644 index 750f68543f2217..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { EuiBadge } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const PhaseErrorMessage = ({ isShowingErrors }: { isShowingErrors: boolean }) => { - return isShowingErrors ? ( - - - - ) : null; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx index 84e955a91ad7ce..b87243bd1a9a18 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -13,19 +13,13 @@ import { EuiDescribedFormGroup, EuiTextColor } from '@elastic/eui'; import { Phases } from '../../../../../../../common/types'; -import { - useFormData, - useFormContext, - UseField, - ToggleField, - NumericField, -} from '../../../../../../shared_imports'; +import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; import { useEditPolicyContext } from '../../../edit_policy_context'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../'; +import { LearnMoreLink, ActiveBadge, DescribedFormField } from '../../'; -import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared'; +import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared_fields'; const i18nTexts = { dataTierAllocation: { @@ -43,15 +37,13 @@ const formFieldPaths = { }; export const ColdPhase: FunctionComponent = () => { - const { originalPolicy } = useEditPolicyContext(); - const form = useFormContext(); + const { policy } = useEditPolicyContext(); const [formData] = useFormData({ watch: [formFieldPaths.enabled], }); const enabled = get(formData, formFieldPaths.enabled); - const isShowingErrors = form.isValid === false; return (
@@ -66,8 +58,7 @@ export const ColdPhase: FunctionComponent = () => { defaultMessage="Cold phase" />

{' '} - {enabled && !isShowingErrors ? : null} - + {enabled && }
} titleSize="s" @@ -128,9 +119,7 @@ export const ColdPhase: FunctionComponent = () => { 'xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel', { defaultMessage: 'Set replicas' } ), - initialValue: Boolean( - originalPolicy.phases.cold?.actions?.allocate?.number_of_replicas - ), + initialValue: Boolean(policy.phases.cold?.actions?.allocate?.number_of_replicas), }} fullWidth > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx similarity index 50% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx index 78ae66327654c8..37323b97edc923 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx @@ -7,53 +7,24 @@ import React, { FunctionComponent, Fragment } from 'react'; import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiTextColor, EuiFormRow } from '@elastic/eui'; -import { DeletePhase as DeletePhaseInterface, Phases } from '../../../../../../common/types'; +import { useFormData, UseField, ToggleField } from '../../../../../../shared_imports'; -import { useFormData } from '../../../../../shared_imports'; +import { ActiveBadge, LearnMoreLink, OptionalLabel } from '../../index'; -import { PhaseValidationErrors } from '../../../../services/policies/policy_validation'; +import { MinAgeInputField, SnapshotPoliciesField } from '../shared_fields'; -import { - ActiveBadge, - LearnMoreLink, - OptionalLabel, - PhaseErrorMessage, - MinAgeInput, - SnapshotPolicies, -} from '../'; -import { useRolloverPath } from './shared'; - -const deleteProperty: keyof Phases = 'delete'; -const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName; - -interface Props { - setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void; - phaseData: DeletePhaseInterface; - isShowingErrors: boolean; - errors?: PhaseValidationErrors; - getUrlForApp: ( - appId: string, - options?: { - path?: string; - absolute?: boolean; - } - ) => string; -} +const formFieldPaths = { + enabled: '_meta.delete.enabled', +}; -export const DeletePhase: FunctionComponent = ({ - setPhaseData, - phaseData, - errors, - isShowingErrors, - getUrlForApp, -}) => { +export const DeletePhase: FunctionComponent = () => { const [formData] = useFormData({ - watch: useRolloverPath, + watch: formFieldPaths.enabled, }); - const hotPhaseRolloverEnabled = get(formData, useRolloverPath); + const enabled = get(formData, formFieldPaths.enabled); return (
@@ -66,8 +37,7 @@ export const DeletePhase: FunctionComponent = ({ defaultMessage="Delete phase" /> {' '} - {phaseData.phaseEnabled && !isShowingErrors ? : null} - + {enabled && }
} titleSize="s" @@ -79,39 +49,23 @@ export const DeletePhase: FunctionComponent = ({ defaultMessage="You no longer need your index. You can define when it is safe to delete it." />

- - } - id={`${deleteProperty}-${phaseProperty('phaseEnabled')}`} - checked={phaseData.phaseEnabled} - onChange={(e) => { - setPhaseData(phaseProperty('phaseEnabled'), e.target.checked); + } fullWidth > - {phaseData.phaseEnabled ? ( - - errors={errors} - phaseData={phaseData} - phase={deleteProperty} - isShowingErrors={isShowingErrors} - setPhaseData={setPhaseData} - rolloverEnabled={hotPhaseRolloverEnabled} - /> - ) : ( -
- )} + {enabled && } - {phaseData.phaseEnabled ? ( + {enabled ? ( @@ -145,11 +99,7 @@ export const DeletePhase: FunctionComponent = ({ } > - setPhaseData(phaseProperty('waitForSnapshotPolicy'), value)} - getUrlForApp={getUrlForApp} - /> + ) : null} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts new file mode 100644 index 00000000000000..488e4e26cfce0c --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/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 { DeletePhase } from './delete_phase'; 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 a184ddf5148b96..629c1388f61fb9 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 @@ -19,7 +19,6 @@ import { import { Phases } from '../../../../../../../common/types'; import { - useFormContext, useFormData, UseField, SelectField, @@ -29,26 +28,24 @@ import { import { i18nTexts } from '../../../i18n_texts'; -import { ROLLOVER_EMPTY_VALIDATION } from '../../../form_validations'; +import { ROLLOVER_EMPTY_VALIDATION } from '../../../form'; import { ROLLOVER_FORM_PATHS } from '../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage } from '../../'; +import { LearnMoreLink, ActiveBadge } from '../../'; -import { Forcemerge, SetPriorityInput, useRolloverPath } from '../shared'; +import { Forcemerge, SetPriorityInput, useRolloverPath } from '../shared_fields'; import { maxSizeStoredUnits, maxAgeUnits } from './constants'; const hotProperty: keyof Phases = 'hot'; export const HotPhase: FunctionComponent = () => { - const form = useFormContext(); const [formData] = useFormData({ watch: useRolloverPath, }); const isRolloverEnabled = get(formData, useRolloverPath); - const isShowingErrors = form.isValid === false; const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false); return ( @@ -62,8 +59,7 @@ export const HotPhase: FunctionComponent = () => { defaultMessage="Hot phase" /> {' '} - {isShowingErrors ? null : } - +
} titleSize="s" diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/cloud_data_tier_callout.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/cloud_data_tier_callout.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.scss similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.scss diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx similarity index 90% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx index 407bb9ea92e855..c1676d7074dbc2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx @@ -10,12 +10,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui'; -import { PhaseWithAllocationAction } from '../../../../../../../../../common/types'; - import { UseField, SelectField, useFormData } from '../../../../../../../../shared_imports'; -import { propertyof } from '../../../../../../../services/policies/policy_validation'; - import { LearnMoreLink } from '../../../../learn_more_link'; import { NodeAttrsDetails } from './node_attrs_details'; @@ -61,9 +57,6 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes }) nodeOptions.sort((a, b) => a.value.localeCompare(b.value)); - // check that this string is a valid property - const nodeAttrsProperty = propertyof('selectedNodeAttrs'); - return ( <> @@ -100,7 +93,7 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes }) ) : undefined, euiFieldProps: { - 'data-test-subj': `${phase}-${nodeAttrsProperty}`, + 'data-test-subj': `${phase}-selectedNodeAttrs`, options: [{ text: i18nTexts.doNotModifyAllocationOption, value: '' }].concat( nodeOptions ), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_attrs_details.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_attrs_details.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_data_provider.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_data_provider.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/types.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/types.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx similarity index 94% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx index b410bd0e6b3b03..b05d49be497cde 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx @@ -21,11 +21,11 @@ interface Props { } export const Forcemerge: React.FunctionComponent = ({ phase }) => { - const { originalPolicy } = useEditPolicyContext(); + const { policy } = useEditPolicyContext(); const initialToggleValue = useMemo(() => { - return Boolean(originalPolicy.phases[phase]?.actions?.forcemerge); - }, [originalPolicy, phase]); + return Boolean(policy.phases[phase]?.actions?.forcemerge); + }, [policy, phase]); return ( = ({ phase }) => { - const phaseIndexPriorityProperty = propertyof('phaseIndexPriority'); return ( = ({ phase }) => { componentProps={{ fullWidth: false, euiFieldProps: { - 'data-test-subj': `${phase}-${phaseIndexPriorityProperty}`, - min: 1, + 'data-test-subj': `${phase}-phaseIndexPriority`, + min: 0, }, }} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx similarity index 68% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx index cc2849b5c8e9c9..e9f9f331e410ab 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx @@ -4,52 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; - +import React from 'react'; +import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ApplicationStart } from 'kibana/public'; import { EuiButtonIcon, EuiCallOut, - EuiComboBox, EuiComboBoxOptionOption, EuiLink, EuiSpacer, } from '@elastic/eui'; -import { useLoadSnapshotPolicies } from '../../../services/api'; +import { UseField, ComboBoxField, useFormData } from '../../../../../../shared_imports'; +import { useLoadSnapshotPolicies } from '../../../../../services/api'; +import { useEditPolicyContext } from '../../../edit_policy_context'; + +const waitForSnapshotFormField = 'phases.delete.actions.wait_for_snapshot.policy'; -interface Props { - value: string; - onChange: (value: string) => void; - getUrlForApp: ApplicationStart['getUrlForApp']; -} -export const SnapshotPolicies: React.FunctionComponent = ({ - value, - onChange, - getUrlForApp, -}) => { +export const SnapshotPoliciesField: React.FunctionComponent = () => { + const { getUrlForApp } = useEditPolicyContext(); const { error, isLoading, data, resendRequest } = useLoadSnapshotPolicies(); + const [formData] = useFormData({ + watch: waitForSnapshotFormField, + }); + + const selectedSnapshotPolicy = get(formData, waitForSnapshotFormField); const policies = data.map((name: string) => ({ label: name, value: name, })); - const onComboChange = (options: EuiComboBoxOptionOption[]) => { - if (options.length > 0) { - onChange(options[0].label); - } else { - onChange(''); - } - }; - - const onCreateOption = (newValue: string) => { - onChange(newValue); - }; - const getUrlForSnapshotPolicyWizard = () => { return getUrlForApp('management', { path: `data/snapshot_restore/add_policy`, @@ -59,14 +46,14 @@ export const SnapshotPolicies: React.FunctionComponent = ({ let calloutContent; if (error) { calloutContent = ( - + <> + <> = ({ } )} /> - + } > = ({ defaultMessage="Refresh this field and enter the name of an existing snapshot policy." />
- + ); } else if (data.length === 0) { calloutContent = ( - + <> = ({ }} /> - + ); - } else if (value && !data.includes(value)) { + } else if (selectedSnapshotPolicy && !data.includes(selectedSnapshotPolicy)) { calloutContent = ( - + <> = ({ }} /> - + ); } return ( - - + path={waitForSnapshotFormField}> + {(field) => { + const singleSelectionArray: [selectedSnapshot?: string] = field.value + ? [field.value] + : []; + + return ( + { + field.setValue(newOption); }, - ] - : [] - } - onChange={onComboChange} - noSuggestions={!!(error || data.length === 0)} - /> + onChange: (options: EuiComboBoxOptionOption[]) => { + if (options.length > 0) { + field.setValue(options[0].label); + } else { + field.setValue(''); + } + }, + }} + /> + ); + }} + {calloutContent} - + ); }; 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 06c16e8bdd5ab4..94fd2ee9edacaa 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 @@ -17,23 +17,17 @@ import { EuiDescribedFormGroup, } from '@elastic/eui'; -import { - useFormData, - UseField, - ToggleField, - useFormContext, - NumericField, -} from '../../../../../../shared_imports'; +import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; import { Phases } from '../../../../../../../common/types'; -import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared'; +import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared_fields'; import { useEditPolicyContext } from '../../../edit_policy_context'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../'; +import { LearnMoreLink, ActiveBadge, DescribedFormField } from '../../'; -import { DataTierAllocationField } from '../shared'; +import { DataTierAllocationField } from '../shared_fields'; const i18nTexts = { shrinkLabel: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.shrinkIndexLabel', { @@ -54,8 +48,7 @@ const formFieldPaths = { }; export const WarmPhase: FunctionComponent = () => { - const { originalPolicy } = useEditPolicyContext(); - const form = useFormContext(); + const { policy } = useEditPolicyContext(); const [formData] = useFormData({ watch: [useRolloverPath, formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover], }); @@ -63,7 +56,6 @@ export const WarmPhase: FunctionComponent = () => { const enabled = get(formData, formFieldPaths.enabled); const hotPhaseRolloverEnabled = get(formData, useRolloverPath); const warmPhaseOnRollover = get(formData, formFieldPaths.warmPhaseOnRollover); - const isShowingErrors = form.isValid === false; return (
@@ -77,8 +69,7 @@ export const WarmPhase: FunctionComponent = () => { defaultMessage="Warm phase" /> {' '} - {enabled && !isShowingErrors ? : null} - + {enabled && }
} titleSize="s" @@ -161,9 +152,7 @@ export const WarmPhase: FunctionComponent = () => { 'xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel', { defaultMessage: 'Set replicas' } ), - initialValue: Boolean( - originalPolicy.phases.warm?.actions?.allocate?.number_of_replicas - ), + initialValue: Boolean(policy.phases.warm?.actions?.allocate?.number_of_replicas), }} fullWidth > @@ -203,7 +192,7 @@ export const WarmPhase: FunctionComponent = () => { 'data-test-subj': 'shrinkSwitch', label: i18nTexts.shrinkLabel, 'aria-label': i18nTexts.shrinkLabel, - initialValue: Boolean(originalPolicy.phases.warm?.actions?.shrink), + initialValue: Boolean(policy.phases.warm?.actions?.shrink), }} fullWidth > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index 7098b018d6dfd4..a8b1680ebde076 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -7,7 +7,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - import { EuiButtonEmpty, EuiCodeBlock, @@ -25,19 +24,15 @@ import { import { SerializedPolicy } from '../../../../../common/types'; import { useFormContext, useFormData } from '../../../../shared_imports'; + import { FormInternal } from '../types'; interface Props { - legacyPolicy: SerializedPolicy; close: () => void; policyName: string; } -export const PolicyJsonFlyout: React.FunctionComponent = ({ - policyName, - close, - legacyPolicy, -}) => { +export const PolicyJsonFlyout: React.FunctionComponent = ({ policyName, close }) => { /** * policy === undefined: we are checking validity * policy === null: we have determined the policy is invalid @@ -51,20 +46,11 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ const updatePolicy = useCallback(async () => { setPolicy(undefined); if (await validateForm()) { - const p = getFormData() as SerializedPolicy; - setPolicy({ - ...legacyPolicy, - phases: { - ...legacyPolicy.phases, - hot: p.phases.hot, - warm: p.phases.warm, - cold: p.phases.cold, - }, - }); + setPolicy(getFormData() as SerializedPolicy); } else { setPolicy(null); } - }, [setPolicy, getFormData, legacyPolicy, validateForm]); + }, [setPolicy, getFormData, validateForm]); useEffect(() => { updatePolicy(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx index c82a420b748571..ebef80871b83dc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx @@ -12,8 +12,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../shared_imports'; import { useLoadPoliciesList } from '../../services/api'; +import { getPolicyByName } from '../../lib/policies'; +import { defaultPolicy } from '../../constants'; import { EditPolicy as PresentationComponent } from './edit_policy'; +import { EditPolicyContextProvider } from './edit_policy_context'; interface RouterProps { policyName: string; @@ -44,6 +47,7 @@ export const EditPolicy: React.FunctionComponent { breadcrumbService.setBreadcrumbs('editPolicy'); }, [breadcrumbService]); + if (isLoading) { return ( + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index 5397f5da2d6bb2..1abbe884c2dc2e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react'; +import React, { Fragment, useEffect, useState, useMemo } from 'react'; +import { get } from 'lodash'; import { RouteComponentProps } from 'react-router-dom'; @@ -16,7 +17,6 @@ import { EuiButton, EuiButtonEmpty, EuiDescribedFormGroup, - EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -30,31 +30,13 @@ import { EuiTitle, } from '@elastic/eui'; -import { useForm, Form } from '../../../shared_imports'; +import { useForm, Form, UseField, TextField, useFormData } from '../../../shared_imports'; import { toasts } from '../../services/notification'; -import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types'; - -import { defaultPolicy } from '../../constants'; - -import { - validatePolicy, - ValidationErrors, - findFirstError, -} from '../../services/policies/policy_validation'; - -import { savePolicy } from '../../services/policies/policy_save'; +import { savePolicy } from './save_policy'; import { - deserializePolicy, - getPolicyByName, - initializeNewPolicy, - legacySerializePolicy, -} from '../../services/policies/policy_serialization'; - -import { - ErrableFormRow, LearnMoreLink, PolicyJsonFlyout, ColdPhase, @@ -63,93 +45,66 @@ import { WarmPhase, } from './components'; -import { schema } from './form_schema'; -import { deserializer } from './deserializer'; -import { createSerializer } from './serializer'; +import { schema, deserializer, createSerializer, createPolicyNameValidations } from './form'; -import { EditPolicyContextProvider } from './edit_policy_context'; +import { useEditPolicyContext } from './edit_policy_context'; +import { FormInternal } from './types'; export interface Props { - policies: PolicyFromES[]; - policyName: string; - getUrlForApp: ( - appId: string, - options?: { - path?: string; - absolute?: boolean; - } - ) => string; history: RouteComponentProps['history']; } -const mergeAllSerializedPolicies = ( - serializedPolicy: SerializedPolicy, - legacySerializedPolicy: SerializedPolicy -): SerializedPolicy => { - return { - ...legacySerializedPolicy, - phases: { - ...legacySerializedPolicy.phases, - hot: serializedPolicy.phases.hot, - warm: serializedPolicy.phases.warm, - cold: serializedPolicy.phases.cold, - }, - }; -}; +const policyNamePath = 'name'; -export const EditPolicy: React.FunctionComponent = ({ - policies, - policyName, - history, - getUrlForApp, -}) => { +export const EditPolicy: React.FunctionComponent = ({ history }) => { useEffect(() => { window.scrollTo(0, 0); }, []); - const [isShowingErrors, setIsShowingErrors] = useState(false); - const [errors, setErrors] = useState(); const [isShowingPolicyJsonFlyout, setIsShowingPolicyJsonFlyout] = useState(false); - - const existingPolicy = getPolicyByName(policies, policyName); + const { + isNewPolicy, + policy: currentPolicy, + existingPolicies, + policyName, + } = useEditPolicyContext(); const serializer = useMemo(() => { - return createSerializer(existingPolicy?.policy); - }, [existingPolicy?.policy]); + return createSerializer(isNewPolicy ? undefined : currentPolicy); + }, [isNewPolicy, currentPolicy]); - const originalPolicy = existingPolicy?.policy ?? defaultPolicy; + const [saveAsNew, setSaveAsNew] = useState(isNewPolicy); + const originalPolicyName: string = isNewPolicy ? '' : policyName!; const { form } = useForm({ schema, - defaultValue: originalPolicy, + defaultValue: { + ...currentPolicy, + name: originalPolicyName, + }, deserializer, serializer, }); - const [policy, setPolicy] = useState(() => - existingPolicy ? deserializePolicy(existingPolicy) : initializeNewPolicy(policyName) + const [formData] = useFormData({ form, watch: policyNamePath }); + const currentPolicyName = get(formData, policyNamePath); + + const policyNameValidations = useMemo( + () => + createPolicyNameValidations({ + originalPolicyName, + policies: existingPolicies, + saveAsNewPolicy: saveAsNew, + }), + [originalPolicyName, existingPolicies, saveAsNew] ); - const isNewPolicy: boolean = !Boolean(existingPolicy); - const [saveAsNew, setSaveAsNew] = useState(isNewPolicy); - const originalPolicyName: string = existingPolicy ? existingPolicy.name : ''; - const backToPolicyList = () => { history.push('/policies'); }; const submit = async () => { - setIsShowingErrors(true); - const { data: formLibPolicy, isValid: newIsValid } = await form.submit(); - const [legacyIsValid, validationErrors] = validatePolicy( - saveAsNew, - policy, - policies, - originalPolicyName - ); - setErrors(validationErrors); - - const isValid = legacyIsValid && newIsValid; + const { data: policy, isValid } = await form.submit(); if (!isValid) { toasts.addDanger( @@ -157,22 +112,11 @@ export const EditPolicy: React.FunctionComponent = ({ defaultMessage: 'Please fix the errors on this page.', }) ); - // This functionality will not be required for once form lib is fully adopted for this form - // because errors are reported as fields are edited. - if (!legacyIsValid) { - const firstError = findFirstError(validationErrors); - const errorRowId = `${firstError ? firstError.replace('.', '-') : ''}-row`; - const element = document.getElementById(errorRowId); - if (element) { - element.scrollIntoView({ block: 'center', inline: 'nearest' }); - } - } } else { - const readSerializedPolicy = () => { - const legacySerializedPolicy = legacySerializePolicy(policy, existingPolicy?.policy); - return mergeAllSerializedPolicies(formLibPolicy, legacySerializedPolicy); - }; - const success = await savePolicy(readSerializedPolicy, isNewPolicy || saveAsNew); + const success = await savePolicy( + { ...policy, name: saveAsNew ? currentPolicyName : originalPolicyName }, + isNewPolicy || saveAsNew + ); if (success) { backToPolicyList(); } @@ -183,248 +127,217 @@ export const EditPolicy: React.FunctionComponent = ({ setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout); }; - const setPhaseData = useCallback( - (phase: keyof LegacyPolicy['phases'], key: string, value: any) => { - setPolicy((nextPolicy) => ({ - ...nextPolicy, - phases: { - ...nextPolicy.phases, - [phase]: { ...nextPolicy.phases[phase], [key]: value }, - }, - })); - }, - [setPolicy] - ); - - const setDeletePhaseData = useCallback( - (key: string, value: any) => setPhaseData('delete', key, value), - [setPhaseData] - ); - return ( - - - - - -

- {isNewPolicy - ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { - defaultMessage: 'Create an index lifecycle policy', - }) - : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { - defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', - values: { originalPolicyName }, - })} -

-
- -
-
- - -

- + +

+ {isNewPolicy + ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { + defaultMessage: 'Create an index lifecycle policy', + }) + : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { + defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', + values: { originalPolicyName }, + })} +

+ + +
+ + + +

+ {' '} - - } - /> -

-
+ />{' '} + + } + /> +

+ - + - {isNewPolicy ? null : ( - - -

- - - - .{' '} + {isNewPolicy ? null : ( + + +

+ + + .{' '} + -

-
- - - - { - setSaveAsNew(e.target.checked); - }} - label={ - - - - } /> - -
- )} - - {saveAsNew || isNewPolicy ? ( - - +

+ + + + + { + setSaveAsNew(e.target.checked); + }} + label={ + -
- } - titleSize="s" - fullWidth - > - + + + )} + + {saveAsNew || isNewPolicy ? ( + + - } - > - { - setPolicy({ ...policy, name: e.target.value }); - }} - /> - - - ) : null} + +
+ } + titleSize="s" + fullWidth + > + + path={policyNamePath} + config={{ + label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', { + defaultMessage: 'Policy name', + }), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage', + { + defaultMessage: + 'A policy name cannot start with an underscore and cannot contain a question mark or a space.', + } + ), + validations: policyNameValidations, + }} + component={TextField} + componentProps={{ + fullWidth: false, + euiFieldProps: { + 'data-test-subj': 'policyNameField', + }, + }} + /> + + ) : null} - + - + - + - + - + - + - + - 0 - } - getUrlForApp={getUrlForApp} - setPhaseData={setDeletePhaseData} - phaseData={policy.phases.delete} - /> + - - - - - - - - {saveAsNew ? ( - - ) : ( - - )} - - - - - + + + + + + + + {saveAsNew ? ( - - - - - - - - {isShowingPolicyJsonFlyout ? ( - - ) : ( + ) : ( + + )} + + + + + - )} - - - - - {isShowingPolicyJsonFlyout ? ( - setIsShowingPolicyJsonFlyout(false)} - /> - ) : null} - -
- - - - + + + + + + + + {isShowingPolicyJsonFlyout ? ( + + ) : ( + + )} + + + + + {isShowingPolicyJsonFlyout ? ( + setIsShowingPolicyJsonFlyout(false)} + /> + ) : null} + + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx index 4748c26d6cec1b..da5f940b1b6c8e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx @@ -5,10 +5,16 @@ */ import React, { createContext, ReactChild, useContext } from 'react'; -import { SerializedPolicy } from '../../../../common/types'; +import { ApplicationStart } from 'kibana/public'; -interface EditPolicyContextValue { - originalPolicy: SerializedPolicy; +import { PolicyFromES, SerializedPolicy } from '../../../../common/types'; + +export interface EditPolicyContextValue { + isNewPolicy: boolean; + policy: SerializedPolicy; + existingPolicies: PolicyFromES[]; + getUrlForApp: ApplicationStart['getUrlForApp']; + policyName?: string; } const EditPolicyContext = createContext(null as any); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts similarity index 82% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts index f0294a5391d21d..5af8807f2dec82 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts @@ -6,17 +6,17 @@ import { produce } from 'immer'; -import { SerializedPolicy } from '../../../../common/types'; +import { SerializedPolicy } from '../../../../../common/types'; -import { splitSizeAndUnits } from '../../services/policies/policy_serialization'; +import { splitSizeAndUnits } from '../../../lib/policies'; -import { determineDataTierAllocationType } from '../../lib'; +import { determineDataTierAllocationType } from '../../../lib'; -import { FormInternal } from './types'; +import { FormInternal } from '../types'; export const deserializer = (policy: SerializedPolicy): FormInternal => { const { - phases: { hot, warm, cold }, + phases: { hot, warm, cold, delete: deletePhase }, } = policy; const _meta: FormInternal['_meta'] = { @@ -37,6 +37,9 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { dataTierAllocationType: determineDataTierAllocationType(cold?.actions), freezeEnabled: Boolean(cold?.actions?.freeze), }, + delete: { + enabled: Boolean(deletePhase), + }, }; return produce( @@ -86,6 +89,14 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { draft._meta.cold.minAgeUnit = minAge.units; } } + + if (draft.phases.delete) { + if (draft.phases.delete.min_age) { + const minAge = splitSizeAndUnits(draft.phases.delete.min_age); + draft.phases.delete.min_age = minAge.size; + draft._meta.delete.minAgeUnit = minAge.units; + } + } } ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts new file mode 100644 index 00000000000000..82fa4788325826 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { deserializer } from './deserializer'; + +export { createSerializer } from './serializer'; + +export { schema } from './schema'; + +export * from './validations'; 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 similarity index 90% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 070f03f74b9549..4d20db40187409 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 @@ -6,18 +6,19 @@ import { i18n } from '@kbn/i18n'; -import { FormSchema, fieldValidators } from '../../../shared_imports'; -import { defaultSetPriority, defaultPhaseIndexPriority } from '../../constants'; +import { FormSchema, fieldValidators } from '../../../../shared_imports'; +import { defaultSetPriority, defaultPhaseIndexPriority } from '../../../constants'; -import { FormInternal } from './types'; +import { FormInternal } from '../types'; import { ifExistsNumberGreaterThanZero, ifExistsNumberNonNegative, rolloverThresholdsValidator, -} from './form_validations'; + minAgeValidator, +} from './validations'; -import { i18nTexts } from './i18n_texts'; +import { i18nTexts } from '../i18n_texts'; const { emptyField, numberGreaterThanField } = fieldValidators; @@ -97,6 +98,18 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, }, + delete: { + enabled: { + defaultValue: false, + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.deletePhase.activateWarmPhaseSwitchLabel', + { defaultMessage: 'Activate delete phase' } + ), + }, + minAgeUnit: { + defaultValue: 'd', + }, + }, }, phases: { hot: { @@ -177,15 +190,7 @@ export const schema: FormSchema = { defaultValue: '0', validations: [ { - validator: (arg) => - numberGreaterThanField({ - than: 0, - allowEquality: true, - message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, - })({ - ...arg, - value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), - }), + validator: minAgeValidator, }, ], }, @@ -256,15 +261,7 @@ export const schema: FormSchema = { defaultValue: '0', validations: [ { - validator: (arg) => - numberGreaterThanField({ - than: 0, - allowEquality: true, - message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, - })({ - ...arg, - value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), - }), + validator: minAgeValidator, }, ], }, @@ -292,5 +289,15 @@ export const schema: FormSchema = { }, }, }, + delete: { + min_age: { + defaultValue: '0', + validations: [ + { + validator: minAgeValidator, + }, + ], + }, + }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts similarity index 90% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts index 564b5a2c4e3979..2274efda426ad1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash'; +import { isEmpty, isNumber } from 'lodash'; -import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../common/types'; +import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../../common/types'; -import { FormInternal, DataAllocationMetaFields } from './types'; -import { isNumber } from '../../services/policies/policy_serialization'; +import { FormInternal, DataAllocationMetaFields } from '../types'; const serializeAllocateAction = ( { dataTierAllocationType, allocationNodeAttribute }: DataAllocationMetaFields, @@ -165,5 +164,22 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( } } + /** + * DELETE PHASE SERIALIZATION + */ + if (policy.phases.delete) { + if (policy.phases.delete.min_age) { + policy.phases.delete.min_age = `${policy.phases.delete.min_age}${_meta.delete.minAgeUnit}`; + } + + if (originalPolicy?.phases.delete?.actions) { + const { wait_for_snapshot: __, ...rest } = originalPolicy.phases.delete.actions; + policy.phases.delete.actions = { + ...policy.phases.delete.actions, + ...rest, + }; + } + } + return policy; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts similarity index 50% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts index 9c855ccb41624f..f2e26a552efc90 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fieldValidators, ValidationFunc } from '../../../shared_imports'; +import { fieldValidators, ValidationFunc, ValidationConfig } from '../../../../shared_imports'; -import { ROLLOVER_FORM_PATHS } from './constants'; +import { ROLLOVER_FORM_PATHS } from '../constants'; -import { i18nTexts } from './i18n_texts'; +import { i18nTexts } from '../i18n_texts'; +import { PolicyFromES } from '../../../../../common/types'; +import { FormInternal } from '../types'; -const { numberGreaterThanField } = fieldValidators; +const { numberGreaterThanField, containsCharsField, emptyField, startsWithField } = fieldValidators; const createIfNumberExistsValidator = ({ than, @@ -46,7 +48,7 @@ export const ifExistsNumberNonNegative = createIfNumberExistsValidator({ * A special validation type used to keep track of validation errors for * the rollover threshold values not being set (e.g., age and doc count) */ -export const ROLLOVER_EMPTY_VALIDATION = 'EMPTY'; +export const ROLLOVER_EMPTY_VALIDATION = 'ROLLOVER_EMPTY_VALIDATION'; /** * An ILM policy requires that for rollover a value must be set for one of the threshold values. @@ -87,3 +89,68 @@ export const rolloverThresholdsValidator: ValidationFunc = ({ form }) => { fields[ROLLOVER_FORM_PATHS.maxSize].clearErrors(ROLLOVER_EMPTY_VALIDATION); } }; + +export const minAgeValidator: ValidationFunc = (arg) => + numberGreaterThanField({ + than: 0, + allowEquality: true, + message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, + })({ + ...arg, + value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), + }); + +export const createPolicyNameValidations = ({ + policies, + saveAsNewPolicy, + originalPolicyName, +}: { + policies: PolicyFromES[]; + saveAsNewPolicy: boolean; + originalPolicyName?: string; +}): Array> => { + return [ + { + validator: emptyField(i18nTexts.editPolicy.errors.policyNameRequiredMessage), + }, + { + validator: startsWithField({ + message: i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage, + char: '_', + }), + }, + { + validator: containsCharsField({ + message: i18nTexts.editPolicy.errors.policyNameContainsInvalidChars, + chars: [',', ' '], + }), + }, + { + validator: (arg) => { + const policyName = arg.value; + if (window.TextEncoder && new window.TextEncoder().encode(policyName).length > 255) { + return { + message: i18nTexts.editPolicy.errors.policyNameTooLongErrorMessage, + }; + } + }, + }, + { + validator: (arg) => { + const policyName = arg.value; + if (saveAsNewPolicy && policyName === originalPolicyName) { + return { + message: i18nTexts.editPolicy.errors.policyNameMustBeDifferentErrorMessage, + }; + } else if (policyName !== originalPolicyName) { + const policyNames = policies.map((existingPolicy) => existingPolicy.name); + if (policyNames.includes(policyName)) { + return { + message: i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, + }; + } + } + }, + }, + ]; +}; 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 1fba69b7634aea..ccd5d3a568fe3a 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 @@ -98,6 +98,42 @@ export const i18nTexts = { defaultMessage: 'Only non-negative numbers are allowed.', } ), + policyNameContainsInvalidChars: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.errors.policyNameContainsInvalidCharsError', + { + defaultMessage: 'A policy name cannot contain spaces or commas.', + } + ), + policyNameAlreadyUsedErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError', + { + defaultMessage: 'That policy name is already used.', + } + ), + policyNameMustBeDifferentErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.differentPolicyNameRequiredError', + { + defaultMessage: 'The policy name must be different.', + } + ), + policyNameRequiredMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError', + { + defaultMessage: 'A policy name is required.', + } + ), + policyNameStartsWithUnderscoreErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError', + { + defaultMessage: 'A policy name cannot start with an underscore.', + } + ), + policyNameTooLongErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError', + { + defaultMessage: 'A policy name cannot be longer than 255 bytes.', + } + ), }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts similarity index 84% rename from x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts index 9cf622e830cb27..e2ab6a8817ef6d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts @@ -3,23 +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 { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { SerializedPolicy } from '../../../../common/types'; -import { savePolicy as savePolicyApi } from '../api'; -import { showApiError } from '../api_errors'; -import { getUiMetricsForPhases, trackUiMetric } from '../ui_metric'; + import { UIM_POLICY_CREATE, UIM_POLICY_UPDATE } from '../../constants'; -import { toasts } from '../notification'; + +import { toasts } from '../../services/notification'; +import { savePolicy as savePolicyApi } from '../../services/api'; +import { getUiMetricsForPhases, trackUiMetric } from '../../services/ui_metric'; +import { showApiError } from '../../services/api_errors'; export const savePolicy = async ( - readSerializedPolicy: () => SerializedPolicy, + serializedPolicy: SerializedPolicy, isNew: boolean ): Promise => { - const serializedPolicy = readSerializedPolicy(); - try { await savePolicyApi(serializedPolicy); } catch (err) { 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 1884f8dbc06191..dc3d8a640e682d 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 @@ -38,6 +38,10 @@ interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { freezeEnabled: boolean; } +interface DeletePhaseMetaFields extends MinAgeField { + enabled: boolean; +} + /** * Describes the shape of data after deserialization. */ @@ -50,5 +54,6 @@ export interface FormInternal extends SerializedPolicy { hot: HotPhaseMetaFields; warm: WarmPhaseMetaFields; cold: ColdPhaseMetaFields; + delete: DeletePhaseMetaFields; }; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts deleted file mode 100644 index 6ada039d45cd99..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts +++ /dev/null @@ -1,88 +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 { DeletePhase, SerializedDeletePhase } from '../../../../common/types'; -import { serializedPhaseInitialization } from '../../constants'; -import { isNumber, splitSizeAndUnits } from './policy_serialization'; -import { - numberRequiredMessage, - PhaseValidationErrors, - positiveNumberRequiredMessage, -} from './policy_validation'; - -const deletePhaseInitialization: DeletePhase = { - phaseEnabled: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - waitForSnapshotPolicy: '', -}; - -export const deletePhaseFromES = (phaseSerialized?: SerializedDeletePhase): DeletePhase => { - const phase = { ...deletePhaseInitialization }; - if (phaseSerialized === undefined || phaseSerialized === null) { - return phase; - } - - phase.phaseEnabled = true; - if (phaseSerialized.min_age) { - const { size: minAge, units: minAgeUnits } = splitSizeAndUnits(phaseSerialized.min_age); - phase.selectedMinimumAge = minAge; - phase.selectedMinimumAgeUnits = minAgeUnits; - } - - if (phaseSerialized.actions) { - const actions = phaseSerialized.actions; - - if (actions.wait_for_snapshot) { - phase.waitForSnapshotPolicy = actions.wait_for_snapshot.policy; - } - } - - return phase; -}; - -export const deletePhaseToES = ( - phase: DeletePhase, - originalEsPhase?: SerializedDeletePhase -): SerializedDeletePhase => { - if (!originalEsPhase) { - originalEsPhase = { ...serializedPhaseInitialization }; - } - const esPhase = { ...originalEsPhase }; - - if (isNumber(phase.selectedMinimumAge)) { - esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`; - } - - esPhase.actions = esPhase.actions ? { ...esPhase.actions } : {}; - - if (phase.waitForSnapshotPolicy) { - esPhase.actions.wait_for_snapshot = { - policy: phase.waitForSnapshotPolicy, - }; - } else { - delete esPhase.actions.wait_for_snapshot; - } - - return esPhase; -}; - -export const validateDeletePhase = (phase: DeletePhase): PhaseValidationErrors => { - if (!phase.phaseEnabled) { - return {}; - } - - const phaseErrors = {} as PhaseValidationErrors; - - // min age needs to be a positive number - if (!isNumber(phase.selectedMinimumAge)) { - phaseErrors.selectedMinimumAge = [numberRequiredMessage]; - } else if (parseInt(phase.selectedMinimumAge, 10) < 0) { - phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage]; - } - - return { ...phaseErrors }; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts deleted file mode 100644 index 19481b39a2c806..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts +++ /dev/null @@ -1,198 +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. - */ -// Prefer importing entire lodash library, e.g. import { get } from "lodash" -// eslint-disable-next-line no-restricted-imports -import cloneDeep from 'lodash/cloneDeep'; -import { deserializePolicy, legacySerializePolicy } from './policy_serialization'; -import { defaultNewDeletePhase } from '../../constants'; - -describe('Policy serialization', () => { - test('serialize a policy using "default" data allocation', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); - - test('serialize a policy using "custom" data allocation', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); - - test('serialize a policy using "custom" data allocation with no node attributes', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - // There should be no allocation action in any phases... - name: 'test', - phases: {}, - }); - }); - - test('serialize a policy using "none" data allocation with no node attributes', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - // There should be no allocation action in any phases... - name: 'test', - phases: {}, - }); - }); - - test('serialization does not alter the original policy', () => { - const originalPolicy = { - name: 'test', - phases: {}, - }; - - const originalClone = cloneDeep(originalPolicy); - - const deserializedPolicy = { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }; - - legacySerializePolicy(deserializedPolicy, originalPolicy); - expect(originalPolicy).toEqual(originalClone); - }); - - test('serialize a policy using "best_compression" codec for forcemerge', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); - - test('de-serialize a policy using "best_compression" codec for forcemerge', () => { - expect( - deserializePolicy({ - modified_date: Date.now().toString(), - name: 'test', - version: 1, - policy: { - name: 'test', - phases: { - hot: { - actions: { - rollover: { - max_age: '30d', - max_size: '50gb', - }, - forcemerge: { - max_num_segments: 1, - index_codec: 'best_compression', - }, - set_priority: { - priority: 100, - }, - }, - }, - }, - }, - }) - ).toEqual({ - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }); - }); - - test('delete "best_compression" codec for forcemerge if disabled in UI', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: {}, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); -}); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts deleted file mode 100644 index 55e9d88dcd383a..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts +++ /dev/null @@ -1,82 +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 { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types'; - -import { defaultNewDeletePhase, serializedPhaseInitialization } from '../../constants'; - -import { deletePhaseFromES, deletePhaseToES } from './delete_phase'; - -export const splitSizeAndUnits = (field: string): { size: string; units: string } => { - let size = ''; - let units = ''; - - const result = /(\d+)(\w+)/.exec(field); - if (result) { - size = result[1]; - units = result[2]; - } - - return { - size, - units, - }; -}; - -export const isNumber = (value: any): boolean => value !== '' && value !== null && isFinite(value); - -export const getPolicyByName = ( - policies: PolicyFromES[] | null | undefined, - policyName: string = '' -): PolicyFromES | undefined => { - if (policies && policies.length > 0) { - return policies.find((policy: PolicyFromES) => policy.name === policyName); - } -}; - -export const initializeNewPolicy = (newPolicyName: string = ''): LegacyPolicy => { - return { - name: newPolicyName, - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }; -}; - -export const deserializePolicy = (policy: PolicyFromES): LegacyPolicy => { - const { - name, - policy: { phases }, - } = policy; - - return { - name, - phases: { - delete: deletePhaseFromES(phases.delete), - }, - }; -}; - -export const legacySerializePolicy = ( - policy: LegacyPolicy, - originalEsPolicy: SerializedPolicy = { - name: policy.name, - phases: { hot: { ...serializedPhaseInitialization } }, - } -): SerializedPolicy => { - const serializedPolicy = { - name: policy.name, - phases: {}, - } as SerializedPolicy; - - if (policy.phases.delete.phaseEnabled) { - serializedPolicy.phases.delete = deletePhaseToES( - policy.phases.delete, - originalEsPolicy.phases.delete - ); - } - return serializedPolicy; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts deleted file mode 100644 index 79c909c433f331..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { DeletePhase, LegacyPolicy, PolicyFromES } from '../../../../common/types'; -import { validateDeletePhase } from './delete_phase'; - -export const propertyof = (propertyName: keyof T & string) => propertyName; - -export const numberRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.numberRequiredError', - { - defaultMessage: 'A number is required.', - } -); - -// TODO validation includes 0 -> should be non-negative number? -export const positiveNumberRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError', - { - defaultMessage: 'Only positive numbers are allowed.', - } -); - -export const positiveNumbersAboveZeroErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError', - { - defaultMessage: 'Only numbers above 0 are allowed.', - } -); - -export const policyNameRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError', - { - defaultMessage: 'A policy name is required.', - } -); - -export const policyNameStartsWithUnderscoreErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError', - { - defaultMessage: 'A policy name cannot start with an underscore.', - } -); -export const policyNameContainsCommaErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError', - { - defaultMessage: 'A policy name cannot include a comma.', - } -); -export const policyNameContainsSpaceErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError', - { - defaultMessage: 'A policy name cannot include a space.', - } -); - -export const policyNameTooLongErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError', - { - defaultMessage: 'A policy name cannot be longer than 255 bytes.', - } -); -export const policyNameMustBeDifferentErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.differentPolicyNameRequiredError', - { - defaultMessage: 'The policy name must be different.', - } -); -export const policyNameAlreadyUsedErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError', - { - defaultMessage: 'That policy name is already used.', - } -); -export type PhaseValidationErrors = { - [P in keyof Partial]: string[]; -}; - -export interface ValidationErrors { - delete: PhaseValidationErrors; - policyName: string[]; -} - -export const validatePolicy = ( - saveAsNew: boolean, - policy: LegacyPolicy, - policies: PolicyFromES[], - originalPolicyName: string -): [boolean, ValidationErrors] => { - const policyNameErrors: string[] = []; - if (!policy.name) { - policyNameErrors.push(policyNameRequiredMessage); - } else { - if (policy.name.startsWith('_')) { - policyNameErrors.push(policyNameStartsWithUnderscoreErrorMessage); - } - if (policy.name.includes(',')) { - policyNameErrors.push(policyNameContainsCommaErrorMessage); - } - if (policy.name.includes(' ')) { - policyNameErrors.push(policyNameContainsSpaceErrorMessage); - } - if (window.TextEncoder && new window.TextEncoder().encode(policy.name).length > 255) { - policyNameErrors.push(policyNameTooLongErrorMessage); - } - - if (saveAsNew && policy.name === originalPolicyName) { - policyNameErrors.push(policyNameMustBeDifferentErrorMessage); - } else if (policy.name !== originalPolicyName) { - const policyNames = policies.map((existingPolicy) => existingPolicy.name); - if (policyNames.includes(policy.name)) { - policyNameErrors.push(policyNameAlreadyUsedErrorMessage); - } - } - } - - const deletePhaseErrors = validateDeletePhase(policy.phases.delete); - const isValid = policyNameErrors.length === 0 && Object.keys(deletePhaseErrors).length === 0; - return [ - isValid, - { - policyName: [...policyNameErrors], - delete: deletePhaseErrors, - }, - ]; -}; - -export const findFirstError = (errors?: ValidationErrors): string | undefined => { - if (!errors) { - return; - } - - if (errors.policyName.length > 0) { - return propertyof('policyName'); - } - - if (Object.keys(errors.delete).length > 0) { - return `${propertyof('delete')}.${Object.keys(errors.delete)[0]}`; - } -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index 023aeba57aa7a6..a127574d5bad04 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -18,6 +18,7 @@ export { getFieldValidityAndErrorMessage, useFormContext, FormSchema, + ValidationConfig, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; @@ -27,6 +28,8 @@ export { NumericField, SelectField, SuperSelectField, + ComboBoxField, + TextField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e7784846598e47..baa4f377910077 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9119,24 +9119,18 @@ "xpack.indexLifecycleMgmt.editPolicy.nodeAttributesReloadButton": "再試行", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsLoadingFailedTitle": "ノード属性詳細を読み込めません", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsReloadButton": "再試行", - "xpack.indexLifecycleMgmt.editPolicy.numberRequiredError": "数字が必要です。", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel": "コールドフェーズのタイミング", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel": "コールドフェーズのタイミングの単位", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel": "削除フェーズのタイミング", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel": "削除フェーズのタイミングの単位", - "xpack.indexLifecycleMgmt.editPolicy.phaseErrorMessage": "エラーを修正してください", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel": "ウォームフェーズのタイミング", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel": "ウォームフェーズのタイミングの単位", "xpack.indexLifecycleMgmt.editPolicy.policiesLoading": "ポリシーを読み込み中…", "xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError": "このポリシー名は既に使用されています。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError": "ポリシー名にはコンマを使用できません。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError": "ポリシー名にはスペースを使用できません。", "xpack.indexLifecycleMgmt.editPolicy.policyNameLabel": "ポリシー名", "xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError": "ポリシー名が必要です。", "xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError": "ポリシー名の頭にアンダーラインを使用することはできません。", "xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError": "ポリシー名は 255 バイト未満である必要があります。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError": "0 よりも大きい数字のみ使用できます。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError": "プラスの数字のみ使用できます。", "xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel": "ロールオーバーからの経過日数", "xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel": "ロールオーバーからの経過時間数", "xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel": "ロールオーバーからの経過時間(マイクロ秒)", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f3cd662bacba71..c4274524928fd4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9128,24 +9128,18 @@ "xpack.indexLifecycleMgmt.editPolicy.nodeAttributesReloadButton": "重试", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsLoadingFailedTitle": "无法加载节点属性详情", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsReloadButton": "重试", - "xpack.indexLifecycleMgmt.editPolicy.numberRequiredError": "数字必填。", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel": "冷阶段计时", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel": "冷阶段计时单位", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel": "删除阶段计时", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel": "删除阶段计时单位", - "xpack.indexLifecycleMgmt.editPolicy.phaseErrorMessage": "修复错误", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel": "温阶段计时", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel": "温阶段计时单位", "xpack.indexLifecycleMgmt.editPolicy.policiesLoading": "正在加载策略……", "xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError": "该策略名称已被使用。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError": "策略名称不能包含逗号。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError": "策略名称不能包含空格。", "xpack.indexLifecycleMgmt.editPolicy.policyNameLabel": "策略名称", "xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError": "策略名称必填。", "xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError": "策略名称不能以下划线开头。", "xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError": "策略名称的长度不能大于 255 字节。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError": "仅允许使用 0 以上的数字。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError": "仅允许使用正数。", "xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel": "天(自滚动更新)", "xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel": "小时(自滚动更新)", "xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel": "微秒(自滚动更新)", From c78cf35ba8bfdff5f61d3932cbacd45a69e4f773 Mon Sep 17 00:00:00 2001 From: Dhruv Bodani Date: Mon, 9 Nov 2020 19:05:05 +0530 Subject: [PATCH 62/81] Added `defaultActionMessage` to index threshold alert UI type definition (#80936) * resolves https://github.com/elastic/kibana/issues/78148 Adds a `defaultActionMessage` to the index threshold alert, so that the `message` parameter for actions will be pre-filled with a useful message --- .../index_threshold/action_context.test.ts | 3 ++ .../index_threshold/action_context.ts | 7 ++-- .../index_threshold/alert_type.test.ts | 4 ++ .../alert_types/index_threshold/alert_type.ts | 12 ++++++ .../builtin_alert_types/threshold/index.ts | 7 +++- .../index_threshold/alert.ts | 41 ++++++++++++++----- .../alert_create_flyout.ts | 5 ++- 7 files changed, 63 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts index 3f5addb77cb331..48847686828a98 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts @@ -25,6 +25,7 @@ describe('ActionContext', () => { date: '2020-01-01T00:00:00.000Z', group: '[group]', value: 42, + function: 'count > 4', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot( @@ -53,6 +54,7 @@ describe('ActionContext', () => { date: '2020-01-01T00:00:00.000Z', group: '[group]', value: 42, + function: 'avg([aggField]) > 4.2', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot( @@ -80,6 +82,7 @@ describe('ActionContext', () => { date: '2020-01-01T00:00:00.000Z', group: '[group]', value: 4, + function: 'count between 4,5', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts index 5135e31e9322c9..9bb0df9d07fd45 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts @@ -27,6 +27,8 @@ export interface BaseActionContext extends AlertInstanceContext { date: string; // the value that met the threshold value: number; + // the function that is used + function: string; } export function addMessages( @@ -42,9 +44,6 @@ export function addMessages( }, }); - const agg = params.aggField ? `${params.aggType}(${params.aggField})` : `${params.aggType}`; - const humanFn = `${agg} ${params.thresholdComparator} ${params.threshold.join(',')}`; - const window = `${params.timeWindowSize}${params.timeWindowUnit}`; const message = i18n.translate( 'xpack.stackAlerts.indexThreshold.alertTypeContextMessageDescription', @@ -55,7 +54,7 @@ export function addMessages( name: alertInfo.name, group: baseContext.group, value: baseContext.value, - function: humanFn, + function: baseContext.function, window, date: baseContext.date, }, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts index 2f0cf3cbbcd16f..d75f3af22ab06a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts @@ -46,6 +46,10 @@ describe('alertType', () => { "description": "The value that exceeded the threshold.", "name": "value", }, + Object { + "description": "A string describing the threshold comparator and threshold", + "name": "function", + }, ], "params": Array [ Object { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 2a1ed429b7fe12..e0a9cd981dac09 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -83,6 +83,13 @@ export function getAlertType(service: Service): AlertType { return { @@ -107,6 +114,7 @@ export function getAlertType(service: Service): AlertType import('./expression')), validate: validateExpression, + defaultActionMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinAlertTypes.threshold.alertDefaultActionMessage', + { + defaultMessage: `alert \\{\\{alertName\\}\\} group \\{\\{context.group\\}\\} value \\{\\{context.value\\}\\} exceeded threshold \\{\\{context.function\\}\\} over \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\} on \\{\\{context.date\\}\\}`, + } + ), requiresAppContext: false, }; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index 92db0458c0639b..c05fa6cf051ff6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -15,6 +15,7 @@ import { ObjectRemover, } from '../../../../../common/lib'; import { createEsDocuments } from './create_test_data'; +import { getAlertType } from '../../../../../../../plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/'; const ALERT_TYPE_ID = '.index-threshold'; const ACTION_TYPE_ID = '.index'; @@ -26,6 +27,8 @@ const ALERT_INTERVALS_TO_WRITE = 5; const ALERT_INTERVAL_SECONDS = 3; const ALERT_INTERVAL_MILLIS = ALERT_INTERVAL_SECONDS * 1000; +const DefaultActionMessage = getAlertType().defaultActionMessage; + // eslint-disable-next-line import/no-default-export export default function alertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -62,6 +65,10 @@ export default function alertTests({ getService }: FtrProviderContext) { await esTestIndexToolOutput.destroy(); }); + it('has a default action message', () => { + expect(DefaultActionMessage).to.be.ok(); + }); + // The tests below create two alerts, one that will fire, one that will // never fire; the tests ensure the ones that should fire, do fire, and // those that shouldn't fire, do not fire. @@ -85,7 +92,7 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(2); for (const doc of docs) { const { group } = doc._source; - const { name, value, title, message } = doc._source.params; + const { name, title, message } = doc._source.params; expect(name).to.be('always fire'); expect(group).to.be('all documents'); @@ -93,9 +100,8 @@ export default function alertTests({ getService }: FtrProviderContext) { // we'll check title and message in this test, but not subsequent ones expect(title).to.be('alert always fire group all documents exceeded threshold'); - const expectedPrefix = `alert always fire group all documents value ${value} exceeded threshold count > -1 over`; - const messagePrefix = message.substr(0, expectedPrefix.length); - expect(messagePrefix).to.be(expectedPrefix); + const messagePattern = /alert always fire group all documents value \d+ exceeded threshold count > -1 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } }); @@ -128,10 +134,13 @@ export default function alertTests({ getService }: FtrProviderContext) { for (const doc of docs) { const { group } = doc._source; - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); if (group === 'group-0') inGroup0++; + + const messagePattern = /alert always fire group group-\d value \d+ exceeded threshold count .+ over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } // there should be 2 docs in group-0, rando split between others @@ -163,9 +172,12 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(2); for (const doc of docs) { - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); + + const messagePattern = /alert always fire group all documents value \d+ exceeded threshold sum\(testedValue\) between 0,1000000 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } }); @@ -195,9 +207,12 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(4); for (const doc of docs) { - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); + + const messagePattern = /alert always fire group all documents value .+ exceeded threshold avg\(testedValue\) .+ 0 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } }); @@ -232,10 +247,13 @@ export default function alertTests({ getService }: FtrProviderContext) { for (const doc of docs) { const { group } = doc._source; - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); if (group === 'group-2') inGroup2++; + + const messagePattern = /alert always fire group group-. value \d+ exceeded threshold max\(testedValue\) .* 0 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } // there should be 2 docs in group-2, rando split between others @@ -274,10 +292,13 @@ export default function alertTests({ getService }: FtrProviderContext) { for (const doc of docs) { const { group } = doc._source; - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); if (group === 'group-0') inGroup0++; + + const messagePattern = /alert always fire group group-. value \d+ exceeded threshold min\(testedValue\) .* 0 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } // there should be 2 docs in group-0, rando split between others @@ -329,7 +350,7 @@ export default function alertTests({ getService }: FtrProviderContext) { name: '{{{alertName}}}', value: '{{{context.value}}}', title: '{{{context.title}}}', - message: '{{{context.message}}}', + message: DefaultActionMessage, }, date: '{{{context.date}}}', // TODO: I wanted to write the alert value here, but how? diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index ee0de582a9bffa..0f6da936f8644d 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -79,10 +79,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); const createdConnectorToastTitle = await pageObjects.common.closeToast(); expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + const messageTextArea = await find.byCssSelector('[data-test-subj="messageTextArea"]'); + expect(await messageTextArea.getAttribute('value')).to.eql( + 'alert {{alertName}} group {{context.group}} value {{context.value}} exceeded threshold {{context.function}} over {{params.timeWindowSize}}{{params.timeWindowUnit}} on {{context.date}}' + ); await testSubjects.setValue('messageTextArea', 'test message '); await testSubjects.click('messageAddVariableButton'); await testSubjects.click('variableMenuButton-0'); - const messageTextArea = await find.byCssSelector('[data-test-subj="messageTextArea"]'); expect(await messageTextArea.getAttribute('value')).to.eql('test message {{alertId}}'); await messageTextArea.type(' some additional text '); From 0217073b8f555b68a7487c5c52325e462a5232af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 9 Nov 2020 11:03:07 -0300 Subject: [PATCH 63/81] [APM] Transition to Elastic charts for all relevant APM charts (#80298) * adding elastic charts * fixing some stuff * refactoring * fixing ts issues * fixing unit test * fix i18n * adding isLoading prop * adding annotations toggle, replacing transaction error rate to elastic chart * adding loading state * adding empty message * fixing i18n * removing unused files * fixing i18n * removing e2e test since elastic charts uses canvas * addressing pr comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apm/e2e/cypress/integration/apm.feature | 3 +- .../cypress/support/step_definitions/apm.ts | 13 - .../ErrorGroupDetails/Distribution/index.tsx | 141 +- .../TransactionDetails/Distribution/index.tsx | 198 ++- .../app/TransactionDetails/index.tsx | 9 +- .../TransactionOverview.test.tsx | 14 +- .../app/TransactionOverview/index.tsx | 23 +- .../components/app/service_overview/index.tsx | 16 +- .../TransactionBreakdownGraph/index.tsx | 143 +- .../shared/TransactionBreakdown/index.tsx | 8 +- .../shared/charts/Histogram/SingleRect.js | 29 - .../Histogram/__test__/Histogram.test.js | 119 -- .../__snapshots__/Histogram.test.js.snap | 1504 ----------------- .../charts/Histogram/__test__/response.json | 106 -- .../shared/charts/Histogram/index.js | 319 ---- .../TransactionLineChart/index.tsx | 70 - .../shared/charts/TransactionCharts/index.tsx | 133 +- .../TransactionCharts/use_formatter.test.tsx | 106 +- .../charts/TransactionCharts/use_formatter.ts | 38 +- .../shared/charts/annotations/index.tsx | 45 + .../shared/charts/chart_container.test.tsx | 91 +- .../shared/charts/chart_container.tsx | 41 +- .../legacy.tsx | 112 -- .../shared/charts/line_chart/index.tsx | 16 +- .../index.tsx | 56 +- .../public/hooks/useTransactionBreakdown.ts | 2 +- .../hooks/useTransactionDistribution.ts | 2 +- .../apm/public/hooks/useTransactionList.ts | 4 +- .../apm/public/hooks/use_annotations.ts | 38 + .../apm/public/selectors/chartSelectors.ts | 41 +- .../lib/errors/distribution/get_buckets.ts | 2 +- .../translations/translations/ja-JP.json | 9 - .../translations/translations/zh-CN.json | 9 - 33 files changed, 718 insertions(+), 2742 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx rename x-pack/plugins/apm/public/components/shared/charts/{erroneous_transactions_rate_chart => transaction_error_rate_chart}/index.tsx (64%) create mode 100644 x-pack/plugins/apm/public/hooks/use_annotations.ts diff --git a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature index 285615108266b9..494a6b5fadb5ba 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature +++ b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature @@ -3,5 +3,4 @@ Feature: APM Scenario: Transaction duration charts Given a user browses the APM UI application When the user inspects the opbeans-node service - Then should redirect to correct path with correct params - And should have correct y-axis ticks + Then should redirect to correct path with correct params \ No newline at end of file diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts index 50c620dca9ddfa..42c2bc7ffd3188 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -29,16 +29,3 @@ Then(`should redirect to correct path with correct params`, () => { cy.url().should('contain', `/app/apm/services/opbeans-node/transactions`); cy.url().should('contain', `transactionType=request`); }); - -Then(`should have correct y-axis ticks`, () => { - const yAxisTick = - '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text'; - - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - - // literal assertions because snapshot() doesn't retry - cy.get(yAxisTick).eq(2).should('have.text', '55 ms'); - cy.get(yAxisTick).eq(1).should('have.text', '28 ms'); - cy.get(yAxisTick).eq(0).should('have.text', '0 ms'); -}); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index e17dd9a9eb038d..a17bf7e93e466a 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -4,31 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + Axis, + Chart, + HistogramBarSeries, + niceTimeFormatter, + Position, + ScaleType, + Settings, + SettingsSpec, + TooltipValue, +} from '@elastic/charts'; import { EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; import d3 from 'd3'; -import { scaleUtc } from 'd3-scale'; -import { mean } from 'lodash'; import React from 'react'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; -import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs'; -// @ts-expect-error -import Histogram from '../../../shared/charts/Histogram'; -import { EmptyMessage } from '../../../shared/EmptyMessage'; - -interface IBucket { - key: number; - count: number | undefined; -} - -// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse) -interface IDistribution { - noHits: boolean; - buckets: IBucket[]; - bucketSize: number; -} +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { ErrorDistributionAPIResponse } from '../../../../../server/lib/errors/distribution/get_distribution'; +import { useTheme } from '../../../../hooks/useTheme'; interface FormattedBucket { x0: number; @@ -37,13 +30,9 @@ interface FormattedBucket { } export function getFormattedBuckets( - buckets: IBucket[], + buckets: ErrorDistributionAPIResponse['buckets'], bucketSize: number -): FormattedBucket[] | null { - if (!buckets) { - return null; - } - +): FormattedBucket[] { return buckets.map(({ count, key }) => { return { x0: key, @@ -54,76 +43,66 @@ export function getFormattedBuckets( } interface Props { - distribution: IDistribution; + distribution: ErrorDistributionAPIResponse; title: React.ReactNode; } -const tooltipHeader = (bucket: FormattedBucket) => - asRelativeDateTimeRange(bucket.x0, bucket.x); - export function ErrorDistribution({ distribution, title }: Props) { + const theme = useTheme(); const buckets = getFormattedBuckets( distribution.buckets, distribution.bucketSize ); - if (!buckets) { - return ( - - ); - } - - const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0; const xMin = d3.min(buckets, (d) => d.x0); - const xMax = d3.max(buckets, (d) => d.x); - const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat(); + const xMax = d3.max(buckets, (d) => d.x0); + + const xFormatter = niceTimeFormatter([xMin, xMax]); + + const tooltipProps: SettingsSpec['tooltip'] = { + headerFormatter: (tooltip: TooltipValue) => { + const serie = buckets.find((bucket) => bucket.x0 === tooltip.value); + if (serie) { + return asRelativeDateTimeRange(serie.x0, serie.x); + } + return `${tooltip.value}`; + }, + }; return (
{title} - bucket.x} - xType="time-utc" - formatX={(value: Date) => { - const time = value.getTime(); - return tickFormat(new Date(time - getTimezoneOffsetInMs(time))); - }} - buckets={buckets} - bucketSize={distribution.bucketSize} - formatYShort={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesShortLabel', { - defaultMessage: '{occCount} occ.', - values: { occCount: value }, - }) - } - formatYLong={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', { - defaultMessage: - '{occCount} {occCount, plural, one {occurrence} other {occurrences}}', - values: { occCount: value }, - }) - } - legends={[ - { - color: theme.euiColorVis1, - // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m - legendValue: numeral(averageValue).format('0a'), - title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', { - defaultMessage: 'Avg.', - }), - legendClickDisabled: true, - }, - ]} - /> +
+ + + + + + +
); } 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 67125d41635a9d..bf1bda793179f3 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 @@ -4,22 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + Axis, + Chart, + ElementClickListener, + GeometryValue, + HistogramBarSeries, + Position, + RectAnnotation, + ScaleType, + Settings, + SettingsSpec, + TooltipValue, + XYChartSeriesIdentifier, +} from '@elastic/charts'; import { EuiIconTip, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { ValuesType } from 'utility-types'; +import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; +import type { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -// @ts-expect-error -import Histogram from '../../../shared/charts/Histogram'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { unit } from '../../../../style/variables'; +import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; interface IChartPoint { x0: number; @@ -31,10 +46,10 @@ interface IChartPoint { } export function getFormattedBuckets( - buckets: DistributionBucket[], - bucketSize: number + buckets?: DistributionBucket[], + bucketSize?: number ) { - if (!buckets) { + if (!buckets || !bucketSize) { return []; } @@ -74,7 +89,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel', { defaultMessage: - '{transCount, plural, =0 {# request} one {# request} other {# requests}}', + '{transCount, plural, =0 {request} one {request} other {requests}}', values: { transCount: t, }, @@ -84,7 +99,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel', { defaultMessage: - '{transCount, plural, =0 {# transaction} one {# transaction} other {# transactions}}', + '{transCount, plural, =0 {transaction} one {transaction} other {transactions}}', values: { transCount: t, }, @@ -95,21 +110,21 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { interface Props { distribution?: TransactionDistributionAPIResponse; urlParams: IUrlParams; - isLoading: boolean; + fetchStatus: FETCH_STATUS; bucketIndex: number; onBucketClick: ( bucket: ValuesType ) => void; } -export function TransactionDistribution(props: Props) { - const { - distribution, - urlParams: { transactionType }, - isLoading, - bucketIndex, - onBucketClick, - } = props; +export function TransactionDistribution({ + distribution, + urlParams: { transactionType }, + fetchStatus, + bucketIndex, + onBucketClick, +}: Props) { + const theme = useTheme(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYShort = useCallback(getFormatYShort(transactionType), [ @@ -122,12 +137,10 @@ export function TransactionDistribution(props: Props) { ]); // no data in response - if (!distribution || distribution.noHits) { - // only show loading state if there is no data - else show stale data until new data has loaded - if (isLoading) { - return ; - } - + if ( + (!distribution || distribution.noHits) && + fetchStatus !== FETCH_STATUS.LOADING + ) { return ( { - return bucket.key === chartPoint.x0; - }); - - return clickedBucket; - } - const buckets = getFormattedBuckets( - distribution.buckets, - distribution.bucketSize + distribution?.buckets, + distribution?.bucketSize ); - const xMax = d3.max(buckets, (d) => d.x) || 0; + const xMin = d3.min(buckets, (d) => d.x0) || 0; + const xMax = d3.max(buckets, (d) => d.x0) || 0; const timeFormatter = getDurationFormatter(xMax); + const tooltipProps: SettingsSpec['tooltip'] = { + headerFormatter: (tooltip: TooltipValue) => { + const serie = buckets.find((bucket) => bucket.x0 === tooltip.value); + if (serie) { + const xFormatted = timeFormatter(serie.x); + const x0Formatted = timeFormatter(serie.x0); + return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; + } + return `${timeFormatter(tooltip.value)}`; + }, + }; + + const onBarClick: ElementClickListener = (elements) => { + const chartPoint = elements[0][0] as GeometryValue; + const clickedBucket = distribution?.buckets.find((bucket) => { + return bucket.key === chartPoint.x; + }); + if (clickedBucket) { + onBucketClick(clickedBucket); + } + }; + + const selectedBucket = buckets[bucketIndex]; + return (
@@ -181,42 +211,66 @@ export function TransactionDistribution(props: Props) { /> - - { - const clickedBucket = getBucketFromChartPoint(chartPoint); - - if (clickedBucket) { - onBucketClick(clickedBucket); - } - }} - formatX={(time: number) => timeFormatter(time).formatted} - formatYShort={formatYShort} - formatYLong={formatYLong} - verticalLineHover={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) - } - backgroundHover={(point: IChartPoint) => - !isEmpty(getBucketFromChartPoint(point)?.samples) - } - tooltipHeader={(point: IChartPoint) => { - const xFormatted = timeFormatter(point.x); - const x0Formatted = timeFormatter(point.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - tooltipFooter={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) && - i18n.translate( - 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip', - { - defaultMessage: 'No sample available for this bucket', - } - ) - } - /> + + + + {selectedBucket && ( + + )} + timeFormatter(time).formatted} + /> + formatYShort(value)} + /> + value} + minBarHeight={2} + id="transactionDurationDistribution" + name={(series: XYChartSeriesIdentifier) => { + const bucketCount = series.splitAccessors.get( + series.yAccessor + ) as number; + return formatYLong(bucketCount); + }} + splitSeriesAccessors={['y']} + xScaleType={ScaleType.Linear} + yScaleType={ScaleType.Linear} + xAccessor="x0" + yAccessors={['y']} + data={buckets} + color={theme.eui.euiColorVis1} + /> + +
); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index efdd7b1f342210..e4c36b028e55cc 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -52,7 +52,11 @@ export function TransactionDetails({ status: distributionStatus, } = useTransactionDistribution(urlParams); - const { data: transactionChartsData } = useTransactionCharts(); + const { + data: transactionChartsData, + status: transactionChartsStatus, + } = useTransactionCharts(); + const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( urlParams ); @@ -121,6 +125,7 @@ export function TransactionDetails({ @@ -131,7 +136,7 @@ export function TransactionDetails({ { diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx index b7d1b93600a73f..c530a7e1489adc 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - fireEvent, - getByText, - queryByLabelText, - render, -} from '@testing-library/react'; +import { fireEvent, getByText, queryByLabelText } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { CoreStart } from 'kibana/public'; import React from 'react'; @@ -20,7 +15,10 @@ import { UrlParamsProvider } from '../../../context/UrlParamsContext'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import * as useFetcherHook from '../../../hooks/useFetcher'; import * as useServiceTransactionTypesHook from '../../../hooks/useServiceTransactionTypes'; -import { disableConsoleWarning } from '../../../utils/testHelpers'; +import { + disableConsoleWarning, + renderWithTheme, +} from '../../../utils/testHelpers'; import { fromQuery } from '../../shared/Links/url_helpers'; import { TransactionOverview } from './'; @@ -54,7 +52,7 @@ function setup({ jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); - return render( + return renderWithTheme( diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 5444d2d521f37e..df9e673ed4847e 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -22,7 +22,7 @@ import React, { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; +import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; @@ -33,11 +33,10 @@ import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter'; +import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; -import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { UserExperienceCallout } from './user_experience_callout'; -import { Correlations } from '../Correlations'; function getRedirectLocation({ urlParams, @@ -83,7 +82,10 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { }) ); - const { data: transactionCharts } = useTransactionCharts(); + const { + data: transactionCharts, + status: transactionChartsStatus, + } = useTransactionCharts(); useTrackPageview({ app: 'apm', path: 'transaction_overview' }); useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 }); @@ -135,12 +137,11 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} - - - + @@ -190,7 +191,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 342152b572f1ec..016ee3daf6b510 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; import { ChartsSyncContextProvider } from '../../../context/charts_sync_context'; -import { ErroneousTransactionsRateChart } from '../../shared/charts/erroneous_transactions_rate_chart'; +import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; @@ -125,19 +125,7 @@ export function ServiceOverview({ {!isRumAgentName(agentName) && ( - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.errorRateChartTitle', - { - defaultMessage: 'Error rate', - } - )} -

-
- -
+
)} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index b908eb8da4d03e..05cae589c19fc6 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -4,62 +4,113 @@ * you may not use this file except in compliance with the Elastic License. */ -import { throttle } from 'lodash'; -import React, { useMemo } from 'react'; +import { + AreaSeries, + Axis, + Chart, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, +} from '@elastic/charts'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useUiTracker } from '../../../../../../observability/public'; -import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; -import { Maybe } from '../../../../../typings/common'; -import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { getEmptySeries } from '../../charts/CustomPlot/getEmptySeries'; -import { TransactionLineChart } from '../../charts/TransactionCharts/TransactionLineChart'; +import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { Annotations } from '../../charts/annotations'; +import { ChartContainer } from '../../charts/chart_container'; +import { onBrushEnd } from '../../charts/helper/helper'; + +const XY_HEIGHT = unit * 16; interface Props { - timeseries: TimeSeries[]; - noHits: boolean; + fetchStatus: FETCH_STATUS; + timeseries?: TimeSeries[]; } -const tickFormatY = (y: Maybe) => { - return asPercent(y ?? 0, 1); -}; +export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) { + const history = useHistory(); + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync2(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; -const formatTooltipValue = (coordinate: Coordinate) => { - return isValidCoordinateValue(coordinate.y) - ? asPercent(coordinate.y, 1) - : NOT_AVAILABLE_LABEL; -}; + useEffect(() => { + if (event.chartId !== 'timeSpentBySpan' && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [chartRef, event]); -function TransactionBreakdownGraph({ timeseries, noHits }: Props) { - const { urlParams } = useUrlParams(); - const { rangeFrom, rangeTo } = urlParams; - const trackApmEvent = useUiTracker({ app: 'apm' }); - const handleHover = useMemo( - () => - throttle(() => trackApmEvent({ metric: 'hover_breakdown_chart' }), 60000), - [trackApmEvent] - ); + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); - const emptySeries = - rangeFrom && rangeTo - ? getEmptySeries( - new Date(rangeFrom).getTime(), - new Date(rangeTo).getTime() - ) - : []; + const xFormatter = niceTimeFormatter([min, max]); return ( - + + + onBrushEnd({ x, history })} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + flatLegend + onPointerUpdate={(currEvent: any) => { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + /> + + asPercent(y ?? 0, 1)} + /> + + + + {timeseries?.length ? ( + timeseries.map((serie) => { + return ( + + ); + }) + ) : ( + // When timeseries is empty, loads an AreaSeries chart to show the default empty message. + + )} + + ); } - -export { TransactionBreakdownGraph }; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 55826497ca3858..9b0c041aaf7b53 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -5,16 +5,13 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; function TransactionBreakdown() { const { data, status } = useTransactionBreakdown(); const { timeseries } = data; - const noHits = isEmpty(timeseries) && status === FETCH_STATUS.SUCCESS; return ( @@ -29,7 +26,10 @@ function TransactionBreakdown() { - +
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js deleted file mode 100644 index ca85ee961f5d87..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -function SingleRect({ innerHeight, marginTop, style, x, width }) { - return ( - - ); -} - -SingleRect.requiresSVG = true; -SingleRect.propTypes = { - x: PropTypes.number.isRequired, -}; - -export default SingleRect; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js deleted file mode 100644 index 03fd039a3401ec..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import d3 from 'd3'; -import { HistogramInner } from '../index'; -import response from './response.json'; -import { - disableConsoleWarning, - toJson, - mountWithTheme, -} from '../../../../../utils/testHelpers'; -import { getFormattedBuckets } from '../../../../app/TransactionDetails/Distribution/index'; -import { - asInteger, - getDurationFormatter, -} from '../../../../../../common/utils/formatters'; - -describe('Histogram', () => { - let mockConsole; - let wrapper; - - const onClick = jest.fn(); - - beforeAll(() => { - mockConsole = disableConsoleWarning('Warning: componentWillReceiveProps'); - }); - - afterAll(() => { - mockConsole.mockRestore(); - }); - - beforeEach(() => { - const buckets = getFormattedBuckets(response.buckets, response.bucketSize); - const xMax = d3.max(buckets, (d) => d.x); - const timeFormatter = getDurationFormatter(xMax); - - wrapper = mountWithTheme( - timeFormatter(time).formatted} - formatYShort={(t) => `${asInteger(t)} occ.`} - formatYLong={(t) => `${asInteger(t)} occurrences`} - tooltipHeader={(bucket) => { - const xFormatted = timeFormatter(bucket.x); - const x0Formatted = timeFormatter(bucket.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - width={800} - /> - ); - }); - - describe('Initially', () => { - it('should have default markup', () => { - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - it('should not show tooltip', () => { - expect(wrapper.find('Tooltip').length).toBe(0); - }); - }); - - describe('when hovering over an empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(2).simulate('mouseOver'); - }); - - it('should not display tooltip', () => { - expect(wrapper.find('Tooltip').length).toBe(0); - }); - }); - - describe('when hovering over a non-empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(7).simulate('mouseOver'); - }); - - it('should display tooltip', () => { - const tooltips = wrapper.find('Tooltip'); - - expect(tooltips.length).toBe(1); - expect(tooltips.prop('header')).toBe('811 - 927 ms'); - expect(tooltips.prop('tooltipPoints')).toEqual([ - { value: '49 occurrences' }, - ]); - expect(tooltips.prop('x')).toEqual(869010); - expect(tooltips.prop('y')).toEqual(27.5); - }); - - it('should have correct markup for tooltip', () => { - const tooltips = wrapper.find('Tooltip'); - expect(toJson(tooltips)).toMatchSnapshot(); - }); - }); - - describe('when clicking on a non-empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(7).simulate('click'); - }); - - it('should call onClick with bucket', () => { - expect(onClick).toHaveBeenCalledWith({ - style: { cursor: 'pointer' }, - xCenter: 869010, - x0: 811076, - x: 926944, - y: 49, - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap deleted file mode 100644 index a31b9735628ab4..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap +++ /dev/null @@ -1,1504 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Histogram Initially should have default markup 1`] = ` -.c0 { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - position: absolute; - top: 0; - left: 0; -} - -
- -
-
- - - - - - - - - - - - - 0 ms - - - - - - 500 ms - - - - - - 1,000 ms - - - - - - 1,500 ms - - - - - - 2,000 ms - - - - - - 2,500 ms - - - - - - 3,000 ms - - - - - - - - - - 0 occ. - - - - - - 28 occ. - - - - - - 55 occ. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-`; - -exports[`Histogram when hovering over a non-empty bucket should have correct markup for tooltip 1`] = ` -.c0 { - margin: 0 16px; - -webkit-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - border: 1px solid #d3dae6; - background: #ffffff; - border-radius: 4px; - font-size: 14px; - color: #000000; -} - -.c1 { - background: #f5f7fa; - border-bottom: 1px solid #d3dae6; - border-radius: 4px 4px 0 0; - padding: 8px; - color: #98a2b3; -} - -.c2 { - margin: 8px; - margin-right: 16px; - font-size: 12px; -} - -.c4 { - color: #98a2b3; - margin: 8px; - font-size: 12px; -} - -.c3 { - color: #69707d; - font-size: 14px; -} - -
- -
- -
- 811 - 927 ms -
-
- -
- -
- 49 occurrences -
-
-
-
- -
- -
-
-
-`; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json deleted file mode 100644 index 302e105dfa997a..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "buckets": [ - { "key": 0, "count": 0 }, - { "key": 115868, "count": 0 }, - { "key": 231736, "count": 0 }, - { "key": 347604, "count": 0 }, - { "key": 463472, "count": 0 }, - { - "key": 579340, - "count": 8, - "samples": [ - { - "transactionId": "99437ee4-08d4-41f5-9b2b-93cc32ec3dfb" - } - ] - }, - { - "key": 695208, - "count": 23, - "samples": [ - { - "transactionId": "d327611b-e999-4942-a94f-c60208940180" - } - ] - }, - { - "key": 811076, - "count": 49, - "samples": [ - { - "transactionId": "99c50a5b-44b4-4289-a3d1-a2815d128192" - } - ] - }, - { - "key": 926944, - "count": 51, - "transactionId": "9706a1ec-23f5-4ce8-97e8-69ce35fb0a9a" - }, - { - "key": 1042812, - "count": 46, - "transactionId": "f8d360c3-dd5e-47b6-b082-9e0bf821d3b2" - }, - { - "key": 1158680, - "count": 13, - "samples": [ - { - "transactionId": "8486d3e2-7f15-48df-aa37-6ee9955adbd2" - } - ] - }, - { - "key": 1274548, - "count": 7, - "transactionId": "54b4b5a7-f065-4cab-9016-534e58f4fc0a" - }, - { - "key": 1390416, - "count": 4, - "transactionId": "8cfac2a3-38e7-4d3a-9792-d008b4bcb867" - }, - { - "key": 1506284, - "count": 3, - "transactionId": "ce3f3bd3-a37c-419e-bb9c-5db956ded149" - }, - { "key": 1622152, "count": 0 }, - { - "key": 1738020, - "count": 4, - "transactionId": "2300174b-85d8-40ba-a6cb-eeba2a49debf" - }, - { "key": 1853888, "count": 0 }, - { "key": 1969756, "count": 0 }, - { - "key": 2085624, - "count": 1, - "transactionId": "774955a4-2ba3-4461-81a6-65759db4805d" - }, - { "key": 2201492, "count": 0 }, - { "key": 2317360, "count": 0 }, - { "key": 2433228, "count": 0 }, - { "key": 2549096, "count": 0 }, - { "key": 2664964, "count": 0 }, - { - "key": 2780832, - "count": 1, - "transactionId": "035d1b9d-af71-46cf-8910-57bd4faf412d" - }, - { - "key": 2896700, - "count": 1, - "transactionId": "4a845b32-9de4-4796-8ef4-d7bbdedc9099" - }, - { "key": 3012568, "count": 0 }, - { - "key": 3128436, - "count": 1, - "transactionId": "68620ffb-7a1b-4f8e-b9bb-009fa5b092be" - } - ], - "bucketSize": 115868, - "defaultBucketIndex": 12 -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js deleted file mode 100644 index 3b2109d68c613d..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { PureComponent } from 'react'; -import d3 from 'd3'; -import { isEmpty } from 'lodash'; -import PropTypes from 'prop-types'; -import { scaleLinear } from 'd3-scale'; -import styled from 'styled-components'; -import SingleRect from './SingleRect'; -import { - XYPlot, - XAxis, - YAxis, - HorizontalGridLines, - VerticalRectSeries, - Voronoi, - makeWidthFlexible, - VerticalGridLines, -} from 'react-vis'; -import { unit } from '../../../../style/variables'; -import Tooltip from '../Tooltip'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { tint } from 'polished'; -import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone'; -import Legends from '../CustomPlot/Legends'; -import StatusText from '../CustomPlot/StatusText'; -import { i18n } from '@kbn/i18n'; -import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; - -const XY_HEIGHT = unit * 10; -const XY_MARGIN = { - top: unit, - left: unit * 5, - right: unit, - bottom: unit * 2, -}; - -const X_TICK_TOTAL = 8; - -// position absolutely to make sure that window resizing/zooming works -const ChartsWrapper = styled.div` - user-select: none; - position: absolute; - top: 0; - left: 0; -`; - -export class HistogramInner extends PureComponent { - constructor(props) { - super(props); - this.state = { - hoveredBucket: {}, - }; - } - - onClick = (bucket) => { - if (this.props.onClick) { - this.props.onClick(bucket); - } - }; - - onHover = (bucket) => { - this.setState({ hoveredBucket: bucket }); - }; - - onBlur = () => { - this.setState({ hoveredBucket: {} }); - }; - - getChartData(items, selectedItem) { - const yMax = d3.max(items, (d) => d.y); - const MINIMUM_BUCKET_SIZE = yMax * 0.02; - - return items.map((item) => { - const padding = (item.x - item.x0) / 20; - return { - ...item, - color: - item === selectedItem - ? theme.euiColorVis1 - : tint(0.5, theme.euiColorVis1), - x0: item.x0 + padding, - x: item.x - padding, - y: item.y > 0 ? Math.max(item.y, MINIMUM_BUCKET_SIZE) : 0, - }; - }); - } - - render() { - const { - backgroundHover, - bucketIndex, - buckets, - bucketSize, - formatX, - formatYShort, - formatYLong, - tooltipFooter, - tooltipHeader, - verticalLineHover, - width: XY_WIDTH, - height, - legends, - } = this.props; - const { hoveredBucket } = this.state; - if (isEmpty(buckets) || XY_WIDTH === 0) { - return null; - } - - const isTimeSeries = - this.props.xType === 'time' || this.props.xType === 'time-utc'; - - const xMin = d3.min(buckets, (d) => d.x0); - const xMax = d3.max(buckets, (d) => d.x); - const yMin = 0; - const yMax = d3.max(buckets, (d) => d.y); - const selectedBucket = buckets[bucketIndex]; - const chartData = this.getChartData(buckets, selectedBucket); - - const x = scaleLinear() - .domain([xMin, xMax]) - .range([XY_MARGIN.left, XY_WIDTH - XY_MARGIN.right]); - - const y = scaleLinear().domain([yMin, yMax]).range([XY_HEIGHT, 0]).nice(); - - const [xMinZone, xMaxZone] = getDomainTZ(xMin, xMax); - const xTickValues = isTimeSeries - ? getTimeTicksTZ({ - domain: [xMinZone, xMaxZone], - totalTicks: X_TICK_TOTAL, - width: XY_WIDTH, - }) - : undefined; - - const xDomain = x.domain(); - const yDomain = y.domain(); - const yTickValues = [0, yDomain[1] / 2, yDomain[1]]; - const shouldShowTooltip = - hoveredBucket.x > 0 && (hoveredBucket.y > 0 || isTimeSeries); - - const showVerticalLineHover = verticalLineHover(hoveredBucket); - const showBackgroundHover = backgroundHover(hoveredBucket); - - const hasValidCoordinates = buckets.some((bucket) => - isValidCoordinateValue(bucket.y) - ); - const noHits = this.props.noHits || !hasValidCoordinates; - - const xyPlotProps = { - dontCheckIfEmpty: true, - xType: this.props.xType, - width: XY_WIDTH, - height: XY_HEIGHT, - margin: XY_MARGIN, - xDomain: xDomain, - yDomain: yDomain, - }; - - const xAxisProps = { - style: { strokeWidth: '1px' }, - marginRight: 10, - tickSize: 0, - tickTotal: X_TICK_TOTAL, - tickFormat: formatX, - tickValues: xTickValues, - }; - - const emptyStateChart = ( - - - - - ); - - return ( -
- - {noHits ? ( - <>{emptyStateChart} - ) : ( - <> - - - - - - {showBackgroundHover && ( - - )} - - {shouldShowTooltip && ( - - )} - - {selectedBucket && ( - - )} - - - - {showVerticalLineHover && hoveredBucket?.x && ( - - )} - - { - return { - ...bucket, - xCenter: (bucket.x0 + bucket.x) / 2, - }; - })} - onClick={this.onClick} - onHover={this.onHover} - onBlur={this.onBlur} - x={(d) => x(d.xCenter)} - y={() => 1} - /> - - - {legends && ( - {}} - truncateLegends={false} - noHits={noHits} - /> - )} - - )} - -
- ); - } -} - -HistogramInner.propTypes = { - backgroundHover: PropTypes.func, - bucketIndex: PropTypes.number, - buckets: PropTypes.array.isRequired, - bucketSize: PropTypes.number.isRequired, - formatX: PropTypes.func, - formatYLong: PropTypes.func, - formatYShort: PropTypes.func, - onClick: PropTypes.func, - tooltipFooter: PropTypes.func, - tooltipHeader: PropTypes.func, - verticalLineHover: PropTypes.func, - width: PropTypes.number.isRequired, - height: PropTypes.number, - xType: PropTypes.string, - legends: PropTypes.array, - noHits: PropTypes.bool, -}; - -HistogramInner.defaultProps = { - backgroundHover: () => null, - formatYLong: (value) => value, - formatYShort: (value) => value, - tooltipFooter: () => null, - tooltipHeader: () => null, - verticalLineHover: () => null, - xType: 'linear', - noHits: false, - height: XY_HEIGHT, -}; - -export default makeWidthFlexible(HistogramInner); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx deleted file mode 100644 index 2e4b51af00d6b7..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback } from 'react'; -import { Coordinate, TimeSeries } from '../../../../../../typings/timeseries'; -import { useLegacyChartsSync as useChartsSync } from '../../../../../hooks/use_charts_sync'; -// @ts-expect-error -import CustomPlot from '../../CustomPlot'; - -interface Props { - series: TimeSeries[]; - truncateLegends?: boolean; - tickFormatY: (y: number) => React.ReactNode; - formatTooltipValue: (c: Coordinate) => React.ReactNode; - yMax?: string | number; - height?: number; - stacked?: boolean; - onHover?: () => void; - visibleLegendCount?: number; - onToggleLegend?: (disabledSeriesState: boolean[]) => void; -} - -function TransactionLineChart(props: Props) { - const { - series, - tickFormatY, - formatTooltipValue, - yMax = 'max', - height, - truncateLegends, - stacked = false, - onHover, - visibleLegendCount, - onToggleLegend, - } = props; - - const syncedChartsProps = useChartsSync(); - - // combine callback for syncedChartsProps.onHover and props.onHover - const combinedOnHover = useCallback( - (hoverX: number) => { - if (onHover) { - onHover(); - } - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps, onHover] - ); - - return ( - - ); -} - -export { TransactionLineChart }; diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index b3c0c3b6de8577..2a5948d0ebf0be 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -20,104 +20,107 @@ import { TRANSACTION_REQUEST, TRANSACTION_ROUTE_CHANGE, } from '../../../../../common/transaction_types'; +import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { Coordinate } from '../../../../../typings/timeseries'; +import { ChartsSyncContextProvider } from '../../../../context/charts_sync_context'; import { LicenseContext } from '../../../../context/LicenseContext'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; -import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { ErroneousTransactionsRateChart } from '../erroneous_transactions_rate_chart/legacy'; import { TransactionBreakdown } from '../../TransactionBreakdown'; -import { - getResponseTimeTickFormatter, - getResponseTimeTooltipFormatter, -} from './helper'; +import { LineChart } from '../line_chart'; +import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; +import { getResponseTimeTickFormatter } from './helper'; import { MLHeader } from './ml_header'; -import { TransactionLineChart } from './TransactionLineChart'; import { useFormatter } from './use_formatter'; interface TransactionChartProps { charts: ITransactionChartData; urlParams: IUrlParams; + fetchStatus: FETCH_STATUS; } export function TransactionCharts({ charts, urlParams, + fetchStatus, }: TransactionChartProps) { const getTPMFormatter = (t: number) => { - const unit = tpmUnit(urlParams.transactionType); - return `${asDecimal(t)} ${unit}`; + return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; }; - const getTPMTooltipFormatter = (p: Coordinate) => { - return isValidCoordinateValue(p.y) - ? getTPMFormatter(p.y) - : NOT_AVAILABLE_LABEL; + const getTPMTooltipFormatter = (y: Coordinate['y']) => { + return isValidCoordinateValue(y) ? getTPMFormatter(y) : NOT_AVAILABLE_LABEL; }; const { transactionType } = urlParams; const { responseTimeSeries, tpmSeries } = charts; - const { formatter, setDisabledSeriesState } = useFormatter( - responseTimeSeries - ); + const { formatter, toggleSerie } = useFormatter(responseTimeSeries); return ( <> - - - - - - - {responseTimeLabel(transactionType)} - - - - {(license) => ( - - )} - - - - - + + + + + + + + {responseTimeLabel(transactionType)} + + + + {(license) => ( + + )} + + + { + if (serie) { + toggleSerie(serie); + } + }} + /> + + - - - - {tpmLabel(transactionType)} - - - - - + + + + {tpmLabel(transactionType)} + + + + + - + - - - - - - - - + + + + + + + + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx index fc873cbda7bf23..958a5db6b66c9e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx @@ -3,38 +3,17 @@ * 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 { SeriesIdentifier } from '@elastic/charts'; +import { renderHook } from '@testing-library/react-hooks'; +import { act } from 'react-test-renderer'; +import { toMicroseconds } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; import { useFormatter } from './use_formatter'; -import { render, fireEvent, act } from '@testing-library/react'; -import { toMicroseconds } from '../../../../../common/utils/formatters'; - -function MockComponent({ - timeSeries, - disabledSeries, - value, -}: { - timeSeries: TimeSeries[]; - disabledSeries: boolean[]; - value: number; -}) { - const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - - const onDisableSeries = () => { - setDisabledSeriesState(disabledSeries); - }; - - return ( -
- - {formatter(value).formatted} -
- ); -} describe('useFormatter', () => { const timeSeries = ([ { + title: 'avg', data: [ { x: 1, y: toMicroseconds(11, 'minutes') }, { x: 2, y: toMicroseconds(1, 'minutes') }, @@ -42,6 +21,7 @@ describe('useFormatter', () => { ], }, { + title: '95th percentile', data: [ { x: 1, y: toMicroseconds(120, 'seconds') }, { x: 2, y: toMicroseconds(1, 'minutes') }, @@ -49,6 +29,7 @@ describe('useFormatter', () => { ], }, { + title: '99th percentile', data: [ { x: 1, y: toMicroseconds(60, 'seconds') }, { x: 2, y: toMicroseconds(5, 'minutes') }, @@ -56,54 +37,47 @@ describe('useFormatter', () => { ], }, ] as unknown) as TimeSeries[]; + it('returns new formatter when disabled series state changes', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + const { result } = renderHook(() => useFormatter(timeSeries)); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('120 s')).toBeInTheDocument(); + + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('120 s'); }); + it('falls back to the first formatter when disabled series is empty', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + const { result } = renderHook(() => useFormatter(timeSeries)); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('2.0 min')).toBeInTheDocument(); - // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - // setDisabledSeriesState([true, true, false]); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - }); - it('falls back to the first formatter when disabled series is all true', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('120 s'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('2.0 min')).toBeInTheDocument(); - // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - // setDisabledSeriesState([true, true, false]); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts index d4694bc3caf1d7..1475ec2934e955 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState, Dispatch, SetStateAction } from 'react'; -import { isEmpty } from 'lodash'; +import { SeriesIdentifier } from '@elastic/charts'; +import { omit } from 'lodash'; +import { useState } from 'react'; import { getDurationFormatter, TimeFormatter, @@ -14,17 +15,36 @@ import { TimeSeries } from '../../../../../typings/timeseries'; import { getMaxY } from './helper'; export const useFormatter = ( - series: TimeSeries[] + series?: TimeSeries[] ): { formatter: TimeFormatter; - setDisabledSeriesState: Dispatch>; + toggleSerie: (disabledSerie: SeriesIdentifier) => void; } => { - const [disabledSeriesState, setDisabledSeriesState] = useState([]); - const visibleSeries = series.filter( - (serie, index) => disabledSeriesState[index] !== true + const [disabledSeries, setDisabledSeries] = useState< + Record + >({}); + + const visibleSeries = series?.filter( + (serie) => disabledSeries[serie.title] === undefined ); - const maxY = getMaxY(isEmpty(visibleSeries) ? series : visibleSeries); + + const maxY = getMaxY(visibleSeries || series || []); const formatter = getDurationFormatter(maxY); - return { formatter, setDisabledSeriesState }; + const toggleSerie = ({ specId }: SeriesIdentifier) => { + if (disabledSeries[specId] !== undefined) { + setDisabledSeries((prevState) => { + return omit(prevState, specId); + }); + } else { + setDisabledSeries((prevState) => { + return { ...prevState, [specId]: 0 }; + }); + } + }; + + return { + formatter, + toggleSerie, + }; }; diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx new file mode 100644 index 00000000000000..683c66b2a96fee --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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, + LineAnnotation, + Position, +} from '@elastic/charts'; +import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { asAbsoluteDateTime } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../hooks/useTheme'; +import { useAnnotations } from '../../../../hooks/use_annotations'; + +export function Annotations() { + const { annotations } = useAnnotations(); + const theme = useTheme(); + + if (!annotations.length) { + return null; + } + + const color = theme.eui.euiColorSecondary; + + return ( + ({ + dataValue: annotation['@timestamp'], + header: asAbsoluteDateTime(annotation['@timestamp']), + details: `${i18n.translate('xpack.apm.chart.annotation.version', { + defaultMessage: 'Version', + })} ${annotation.text}`, + }))} + style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }} + marker={} + markerPosition={Position.Top} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx index 409cb69575ca9c..c0e8f869ce647e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx @@ -5,30 +5,97 @@ */ import { render } from '@testing-library/react'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { ChartContainer } from './chart_container'; describe('ChartContainer', () => { - describe('when isLoading is true', () => { - it('shows loading the indicator', () => { - const component = render( - + describe('loading indicator', () => { + it('shows loading when status equals to Loading or Pending and has no data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByTestId } = render( + +
My amazing component
+
+ ); + + expect(queryAllByTestId('loading')[0]).toBeInTheDocument(); + }); + }); + it('does not show loading when status equals to Loading or Pending and has data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByText } = render( + +
My amazing component
+
+ ); + expect(queryAllByText('My amazing component')[0]).toBeInTheDocument(); + }); + }); + }); + + describe('failure indicator', () => { + it('shows failure message when status equals to Failure and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.getByTestId('loading')).toBeInTheDocument(); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); + }); + it('shows failure message when status equals to Failure and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); }); }); - describe('when isLoading is false', () => { - it('does not show the loading indicator', () => { - const component = render( - + describe('render component', () => { + it('shows children component when status Success and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + expect(getByText('My amazing component')).toBeInTheDocument(); + }); + it('shows children component when status Success and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect(getByText('My amazing component')).toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index a6f579308597ff..b4486f1e9b94ad 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -3,27 +3,56 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiLoadingChart } from '@elastic/eui'; + +import { EuiLoadingChart, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; interface Props { - isLoading: boolean; + hasData: boolean; + status: FETCH_STATUS; height: number; children: React.ReactNode; } -export function ChartContainer({ isLoading, children, height }: Props) { +export function ChartContainer({ children, height, status, hasData }: Props) { + if ( + !hasData && + (status === FETCH_STATUS.LOADING || status === FETCH_STATUS.PENDING) + ) { + return ; + } + + if (status === FETCH_STATUS.FAILURE) { + return ; + } + + return
{children}
; +} + +function LoadingChartPlaceholder({ height }: { height: number }) { return (
- {isLoading && } - {children} +
); } + +function FailedChartPlaceholder({ height }: { height: number }) { + return ( + + {i18n.translate('xpack.apm.chart.error', { + defaultMessage: + 'An error happened when trying to fetch data. Please try again', + })} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx deleted file mode 100644 index 29102f606414f9..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.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 { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { i18n } from '@kbn/i18n'; -import { max } from 'lodash'; -import React, { useCallback } from 'react'; -import { useParams } from 'react-router-dom'; -import { asPercent } from '../../../../../common/utils/formatters'; -import { useLegacyChartsSync as useChartsSync } from '../../../../hooks/use_charts_sync'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { callApmApi } from '../../../../services/rest/createCallApmApi'; -// @ts-expect-error -import CustomPlot from '../CustomPlot'; - -const tickFormatY = (y?: number | null) => { - return asPercent(y || 0, 1); -}; - -/** - * "Legacy" version of this chart using react-vis charts. See index.tsx for the - * Elastic Charts version. - * - * This will be removed with #70290. - */ -export function ErroneousTransactionsRateChart() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { urlParams, uiFilters } = useUrlParams(); - const syncedChartsProps = useChartsSync(); - - const { start, end, transactionType, transactionName } = urlParams; - - const { data } = useFetcher(() => { - if (serviceName && start && end) { - return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/error_rate', - params: { - path: { - serviceName, - }, - query: { - start, - end, - transactionType, - transactionName, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters, transactionType, transactionName]); - - const combinedOnHover = useCallback( - (hoverX: number) => { - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps] - ); - - const errorRates = data?.transactionErrorRate || []; - const maxRate = max(errorRates.map((errorRate) => errorRate.y)); - - return ( - - - - {i18n.translate('xpack.apm.errorRateChart.title', { - defaultMessage: 'Transaction error rate', - })} - - - - - Number.isFinite(y) ? tickFormatY(y) : 'N/A' - } - /> - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 3f2a08ecb76415..507acc49d89dbd 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -20,15 +20,17 @@ import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; +import { Annotations } from '../annotations'; import { ChartContainer } from '../chart_container'; import { onBrushEnd } from '../helper/helper'; interface Props { id: string; - isLoading: boolean; + fetchStatus: FETCH_STATUS; onToggleLegend?: LegendItemListener; timeseries: TimeSeries[]; /** @@ -38,18 +40,20 @@ interface Props { /** * Formatter for legend and tooltip values */ - yTickFormat: (y: number) => string; + yTickFormat?: (y: number) => string; + showAnnotations?: boolean; } const XY_HEIGHT = unit * 16; export function LineChart({ id, - isLoading, + fetchStatus, onToggleLegend, timeseries, yLabelFormat, yTickFormat, + showAnnotations = true, }: Props) { const history = useHistory(); const chartRef = React.createRef(); @@ -84,7 +88,7 @@ export function LineChart({ ); return ( - + onBrushEnd({ x, history })} @@ -115,11 +119,13 @@ export function LineChart({ id="y-axis" ticks={3} position={Position.Left} - tickFormat={yTickFormat} + tickFormat={yTickFormat ? yTickFormat : yLabelFormat} labelFormat={yLabelFormat} showGridLines /> + {showAnnotations && } + {timeseries.map((serie) => { return ( (); const { urlParams, uiFilters } = useUrlParams(); @@ -56,25 +61,32 @@ export function ErroneousTransactionsRateChart() { const errorRates = data?.transactionErrorRate || []; return ( - + + +

+ {i18n.translate('xpack.apm.errorRate', { + defaultMessage: 'Error rate', + })} +

+
+ +
); } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts index 08d2300c3254a6..0705383ecb0cab 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts @@ -15,7 +15,7 @@ export function useTransactionBreakdown() { uiFilters, } = useUrlParams(); - const { data = { timeseries: [] }, error, status } = useFetcher( + const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { return callApmApi({ diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts index a5096a314388ca..8c76225d03486f 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts @@ -10,7 +10,7 @@ import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; import { useUiFilters } from '../context/UrlParamsContext'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index 9c3a18b9c0d0d5..b2c2cc30f78ec3 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -14,8 +14,8 @@ type TransactionsAPIResponse = APIReturnType< '/api/apm/services/{serviceName}/transaction_groups' >; -const DEFAULT_RESPONSE: TransactionsAPIResponse = { - items: [], +const DEFAULT_RESPONSE: Partial = { + items: undefined, isAggregationAccurate: true, bucketSize: 0, }; diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/hooks/use_annotations.ts new file mode 100644 index 00000000000000..2b1c2bec52b3db --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_annotations.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { useParams } from 'react-router-dom'; +import { callApmApi } from '../services/rest/createCallApmApi'; +import { useFetcher } from './useFetcher'; +import { useUrlParams } from './useUrlParams'; + +const INITIAL_STATE = { annotations: [] }; + +export function useAnnotations() { + const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { environment } = uiFilters; + + const { data = INITIAL_STATE } = useFetcher(() => { + if (start && end && serviceName) { + return callApmApi({ + pathname: '/api/apm/services/{serviceName}/annotation/search', + params: { + path: { + serviceName, + }, + query: { + start, + end, + environment, + }, + }, + }); + } + }, [start, end, environment, serviceName]); + + return data; +} diff --git a/x-pack/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/plugins/apm/public/selectors/chartSelectors.ts index 8c6093859f969d..450f02f70c6a42 100644 --- a/x-pack/plugins/apm/public/selectors/chartSelectors.ts +++ b/x-pack/plugins/apm/public/selectors/chartSelectors.ts @@ -31,40 +31,37 @@ export interface ITpmBucket { } export interface ITransactionChartData { - tpmSeries: ITpmBucket[]; - responseTimeSeries: TimeSeries[]; + tpmSeries?: ITpmBucket[]; + responseTimeSeries?: TimeSeries[]; mlJobId: string | undefined; } -const INITIAL_DATA = { - apmTimeseries: { - responseTimes: { - avg: [], - p95: [], - p99: [], - }, - tpmBuckets: [], - overallAvgDuration: null, - }, +const INITIAL_DATA: Partial = { + apmTimeseries: undefined, anomalyTimeseries: undefined, }; export function getTransactionCharts( { transactionType }: IUrlParams, - { apmTimeseries, anomalyTimeseries }: TimeSeriesAPIResponse = INITIAL_DATA + charts = INITIAL_DATA ): ITransactionChartData { - const tpmSeries = getTpmSeries(apmTimeseries, transactionType); - - const responseTimeSeries = getResponseTimeSeries({ - apmTimeseries, - anomalyTimeseries, - }); + const { apmTimeseries, anomalyTimeseries } = charts; - return { - tpmSeries, - responseTimeSeries, + const transactionCharts: ITransactionChartData = { + tpmSeries: undefined, + responseTimeSeries: undefined, mlJobId: anomalyTimeseries?.jobId, }; + + if (apmTimeseries) { + transactionCharts.tpmSeries = getTpmSeries(apmTimeseries, transactionType); + + transactionCharts.responseTimeSeries = getResponseTimeSeries({ + apmTimeseries, + anomalyTimeseries, + }); + } + return transactionCharts; } export function getResponseTimeSeries({ diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index a42710947a7924..b12dd73a20986b 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -73,6 +73,6 @@ export async function getBuckets({ return { noHits: resp.hits.total.value === 0, - buckets, + buckets: resp.hits.total.value > 0 ? buckets : [], }; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index baa4f377910077..485b24dced3463 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4875,22 +4875,15 @@ "xpack.apm.error.prompt.title": "申し訳ございませんが、エラーが発生しました :(", "xpack.apm.errorCountAlert.name": "エラー数しきい値", "xpack.apm.errorCountAlertTrigger.errors": " エラー", - "xpack.apm.errorGroupDetails.avgLabel": "平均", "xpack.apm.errorGroupDetails.culpritLabel": "原因", "xpack.apm.errorGroupDetails.errorGroupTitle": "エラーグループ {errorGroupId}", "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "エラーのオカレンス", "xpack.apm.errorGroupDetails.exceptionMessageLabel": "例外メッセージ", "xpack.apm.errorGroupDetails.logMessageLabel": "ログメッセージ", - "xpack.apm.errorGroupDetails.noErrorsLabel": "エラーが見つかりませんでした", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "オカレンス", - "xpack.apm.errorGroupDetails.occurrencesLongLabel": "{occCount} {occCount, plural, one {件の発生} other {件の発生}}", - "xpack.apm.errorGroupDetails.occurrencesShortLabel": "{occCount} 件", "xpack.apm.errorGroupDetails.relatedTransactionSample": "関連トランザクションサンプル", "xpack.apm.errorGroupDetails.unhandledLabel": "未対応", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "ディスカバリで {occurrencesCount} 件の{occurrencesCount, plural, one {ドキュメント} other {ドキュメント}}を表示。", - "xpack.apm.errorRateChart.avgLabel": "平均", - "xpack.apm.errorRateChart.rateLabel": "レート", - "xpack.apm.errorRateChart.title": "トランザクションエラー率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因", "xpack.apm.errorsTable.groupIdColumnDescription": "スタックトレースのハッシュ。動的パラメータのため、エラーメッセージが異なる場合でも、類似したエラーをグループ化します。", "xpack.apm.errorsTable.groupIdColumnLabel": "グループ ID", @@ -4917,7 +4910,6 @@ "xpack.apm.header.badge.readOnly.text": "読み込み専用", "xpack.apm.header.badge.readOnly.tooltip": "を保存できませんでした", "xpack.apm.helpMenu.upgradeAssistantLink": "アップグレードアシスタント", - "xpack.apm.histogram.plot.noDataLabel": "この時間範囲のデータがありません。", "xpack.apm.home.alertsMenu.alerts": "アラート", "xpack.apm.home.alertsMenu.createAnomalyAlert": "異常アラートを作成", "xpack.apm.home.alertsMenu.createThresholdAlert": "しきい値アラートを作成", @@ -5256,7 +5248,6 @@ "xpack.apm.transactionDetails.traceNotFound": "選択されたトレースが見つかりません", "xpack.apm.transactionDetails.traceSampleTitle": "トレースのサンプル", "xpack.apm.transactionDetails.transactionLabel": "トランザクション", - "xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip": "このバケットに利用可能なサンプルがありません", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel": "{transCount, plural, =0 {# request} 1 {# 件のリクエスト} other {# 件のリクエスト}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel": "{transCount, plural, =0 {# transaction} 1 {# 件のトランザクション} other {# 件のトランザクション}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel": "{transCount} {transType, select, request {件のリクエスト} other {件のトランザクション}}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c4274524928fd4..98d13011d3306f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4877,22 +4877,15 @@ "xpack.apm.error.prompt.title": "抱歉,发生错误 :(", "xpack.apm.errorCountAlert.name": "错误计数阈值", "xpack.apm.errorCountAlertTrigger.errors": " 错误", - "xpack.apm.errorGroupDetails.avgLabel": "平均", "xpack.apm.errorGroupDetails.culpritLabel": "原因", "xpack.apm.errorGroupDetails.errorGroupTitle": "错误组 {errorGroupId}", "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "错误发生", "xpack.apm.errorGroupDetails.exceptionMessageLabel": "异常消息", "xpack.apm.errorGroupDetails.logMessageLabel": "日志消息", - "xpack.apm.errorGroupDetails.noErrorsLabel": "未找到任何错误", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "发生次数", - "xpack.apm.errorGroupDetails.occurrencesLongLabel": "{occCount} 次{occCount, plural, one {出现} other {出现}}", - "xpack.apm.errorGroupDetails.occurrencesShortLabel": "{occCount} 次发生", "xpack.apm.errorGroupDetails.relatedTransactionSample": "相关的事务样本", "xpack.apm.errorGroupDetails.unhandledLabel": "未处理", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "在 Discover 查看 {occurrencesCount} 个 {occurrencesCount, plural, one {匹配项} other {匹配项}}。", - "xpack.apm.errorRateChart.avgLabel": "平均", - "xpack.apm.errorRateChart.rateLabel": "比率", - "xpack.apm.errorRateChart.title": "事务错误率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "错误消息和原因", "xpack.apm.errorsTable.groupIdColumnDescription": "堆栈跟踪的哈希。将类似错误分组在一起,即使因动态参数造成错误消息不同。", "xpack.apm.errorsTable.groupIdColumnLabel": "组 ID", @@ -4919,7 +4912,6 @@ "xpack.apm.header.badge.readOnly.text": "只读", "xpack.apm.header.badge.readOnly.tooltip": "无法保存", "xpack.apm.helpMenu.upgradeAssistantLink": "升级助手", - "xpack.apm.histogram.plot.noDataLabel": "此时间范围内没有数据。", "xpack.apm.home.alertsMenu.alerts": "告警", "xpack.apm.home.alertsMenu.createAnomalyAlert": "创建异常告警", "xpack.apm.home.alertsMenu.createThresholdAlert": "创建阈值告警", @@ -5260,7 +5252,6 @@ "xpack.apm.transactionDetails.traceNotFound": "找不到所选跟踪", "xpack.apm.transactionDetails.traceSampleTitle": "跟踪样例", "xpack.apm.transactionDetails.transactionLabel": "事务", - "xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip": "此存储桶没有可用样例", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel": "{transCount, plural, =0 {# 个请求} one {# 个请求} other {# 个请求}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel": "{transCount, plural, =0 {# 个事务} one {# 个事务} other {# 个事务}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel": "{transCount} 个{transType, select, request {请求} other {事务}}", From 09aec4defd1d20f6e0fb4089359ffd8de11ff966 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 9 Nov 2020 15:21:15 +0100 Subject: [PATCH 64/81] Indexpattern edit field formatter API fix for scripted field (#82876) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../index_patterns/index_pattern.test.ts | 18 ++++++++++++++++++ .../index_patterns/index_pattern.ts | 18 +++++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index 9085ae07bbe3ea..145901509d1c58 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -196,6 +196,24 @@ describe('IndexPattern', () => { }); }); + describe('getFormatterForField', () => { + test('should return the default one for empty objects', () => { + indexPattern.setFieldFormat('scriptedFieldWithEmptyFormatter', {}); + expect( + indexPattern.getFormatterForField({ + name: 'scriptedFieldWithEmptyFormatter', + type: 'number', + esTypes: ['long'], + }) + ).toEqual( + expect.objectContaining({ + convert: expect.any(Function), + getConverterFor: expect.any(Function), + }) + ); + }); + }); + describe('toSpec', () => { test('should match snapshot', () => { const formatter = { diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index a0f27078543a9c..4508d7b1d90821 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -291,15 +291,15 @@ export class IndexPattern implements IIndexPattern { getFormatterForField( field: IndexPatternField | IndexPatternField['spec'] | IFieldType ): FieldFormat { - const formatSpec = this.fieldFormatMap[field.name]; - if (formatSpec) { - return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); - } else { - return this.fieldFormats.getDefaultInstance( - field.type as KBN_FIELD_TYPES, - field.esTypes as ES_FIELD_TYPES[] - ); + const fieldFormat = this.getFormatterForFieldNoDefault(field.name); + if (fieldFormat) { + return fieldFormat; } + + return this.fieldFormats.getDefaultInstance( + field.type as KBN_FIELD_TYPES, + field.esTypes as ES_FIELD_TYPES[] + ); } /** @@ -308,7 +308,7 @@ export class IndexPattern implements IIndexPattern { */ getFormatterForFieldNoDefault(fieldname: string) { const formatSpec = this.fieldFormatMap[fieldname]; - if (formatSpec) { + if (formatSpec?.id) { return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); } } From 97e2dc853b5d0d12b87691bb447f6475de0bbfec Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 9 Nov 2020 15:22:21 +0100 Subject: [PATCH 65/81] [Lens] Extend Datasource props validation with VisualizationGroups (#82607) * :sparkles: First pass with visualization validation + error messages * :fire: Remove indexpattern error handling for now * :label: Fix type issues * :white_check_mark: Add getErrorMessage test for data table * :white_check_mark: Add tests for pie and metric error messages * :globe_with_meridians: Fix i18n checks issues * :bug: Fix last issue * :white_check_mark: Add more tests for the XY visualization validation code * :ok_hand: Included all feedback from first review * :pencil2: Off by one message * :globe_with_meridians: Fix i18n duplicate id * :globe_with_meridians: Fix last i18n issue * :bug: Fixed a hook reflow issue * :recycle:+:white_check_mark: Reworked validation flow + tests * :label: Fix type issue * :bug: Improved XY corner cases validation logic * :bug: Fix empty datatable scenario * :sparkles: + :white_check_mark: Improved error messages for invalid datasources + tests * :globe_with_meridians: Add missing i18n translation * :label: Fix type issues * :globe_with_meridians: Fix i18n issues * :sparkles: Filter out suggestions which fail to build * :truck: Migrate datatable validation logic to the building phase, handling it as building state * :label: Fix type issue * :pencil2: Add comment for future enhancements * :pencil2: Updated comment * :world_with_meridians: Refactor axis labels * :globe_with_meridians: Reworked few validation messages * :bug: Fix break down validation + percentage charts * :white_check_mark: Align tests with new validation logic * :recycle: Fix suggestion panel validation to match main panel * :globe_with_meridians: Fix i18n issues * :wrench: Fix some refs for validation checks in suggestions * :bug: Fix missing key prop in multiple errors scenario * :bug: Fix swtich issue from XY to partition * :globe_with_meridians: Fix i18n messages and aligned tests * :bug: Fix suggestions switching bug * :refactor: Add more validation + refactored validation logic in a single place * :pencil2: Add note about lint hooks disable rule * :rotating_light: Fix linting issue * :building_construction: Add infra API for datasource advanced validation * :white_check_mark: Align tests with new API * :white_check_mark: Fix type issues in tests * :ok_hand: Early exists added * :sparkles: Add layers groups to the API * :white_check_mark: Fix some broken test after the validation change * :ok_hand: Move to disctionary shape Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../editor_frame/config_panel/layer_panel.tsx | 1 + .../editor_frame/editor_frame.test.tsx | 9 ++++--- .../editor_frame/state_helpers.ts | 26 +++++++++++++++++-- .../dimension_panel/dimension_panel.test.tsx | 1 + .../dimension_panel/droppable.test.ts | 1 + .../indexpattern_datasource/indexpattern.tsx | 2 +- x-pack/plugins/lens/public/types.ts | 6 ++++- 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index c9d99bcfb6d8d8..0332f11aa78b34 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -451,6 +451,7 @@ export function LayerPanel( columnId: activeId, filterOperations: activeGroup.filterOperations, suggestedPriority: activeGroup?.suggestedPriority, + dimensionGroups: groups, setState: (newState: unknown) => { props.updateAll( datasourceId, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index f7a6f0597bf9c3..b3ea14efbae800 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -601,7 +601,8 @@ describe('editor_frame', () => { setDatasourceState(updatedState); }); - expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(2); + // validation requires to calls this getConfiguration API + expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6); expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith( expect.objectContaining({ state: updatedState, @@ -680,7 +681,8 @@ describe('editor_frame', () => { setDatasourceState({}); }); - expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(2); + // validation requires to calls this getConfiguration API + expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6); expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith( expect.objectContaining({ frame: expect.objectContaining({ @@ -1193,7 +1195,8 @@ describe('editor_frame', () => { instance.find('[data-test-subj="lnsSuggestion"]').at(2).simulate('click'); }); - expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(1); + // validation requires to calls this getConfiguration API + expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(4); expect(mockVisualization.getConfiguration).toHaveBeenCalledWith( expect.objectContaining({ state: suggestionVisState, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 28ad6c531e2554..647c0f3ac9cca7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -6,7 +6,13 @@ import { SavedObjectReference } from 'kibana/public'; import { Ast } from '@kbn/interpreter/common'; -import { Datasource, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../../types'; +import { + Datasource, + DatasourcePublicAPI, + FramePublicAPI, + Visualization, + VisualizationDimensionGroupConfig, +} from '../../types'; import { buildExpression } from './expression_helpers'; import { Document } from '../../persistence/saved_object_store'; import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; @@ -104,8 +110,24 @@ export const validateDatasourceAndVisualization = ( longMessage: string; }> | undefined => { + const layersGroups = + currentVisualizationState && + currentVisualization + ?.getLayerIds(currentVisualizationState) + .reduce>((memo, layerId) => { + const groups = currentVisualization?.getConfiguration({ + frame: frameAPI, + layerId, + state: currentVisualizationState, + }).groups; + if (groups) { + memo[layerId] = groups; + } + return memo; + }, {}); + const datasourceValidationErrors = currentDatasourceState - ? currentDataSource?.getErrorMessages(currentDatasourceState) + ? currentDataSource?.getErrorMessages(currentDatasourceState, layersGroups) : undefined; const visualizationValidationErrors = currentVisualizationState diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 829bd333ce2cc7..92a4dad14dd257 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -174,6 +174,7 @@ describe('IndexPatternDimensionEditorPanel', () => { } as unknown) as DataPublicPluginStart['fieldFormats'], } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, + dimensionGroups: [], }; jest.clearAllMocks(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index bbd1d4e0ae3ab6..dd696f8be357fd 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -146,6 +146,7 @@ describe('IndexPatternDimensionEditorPanel', () => { } as unknown) as DataPublicPluginStart['fieldFormats'], } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, + dimensionGroups: [], }; jest.clearAllMocks(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index ecca1b878e9a7d..fa106e90d518ab 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -343,7 +343,7 @@ export function getIndexPatternDatasource({ getDatasourceSuggestionsFromCurrentState, getDatasourceSuggestionsForVisualizeField, - getErrorMessages(state) { + getErrorMessages(state, layersGroups) { if (!state) { return; } diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 3c96579fdc9431..4ad849c5d441e4 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -184,7 +184,10 @@ export interface Datasource { ) => Array>; getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI; - getErrorMessages: (state: T) => Array<{ shortMessage: string; longMessage: string }> | undefined; + getErrorMessages: ( + state: T, + layersGroups?: Record + ) => Array<{ shortMessage: string; longMessage: string }> | undefined; /** * uniqueLabels of dimensions exposed for aria-labels of dragged dimensions */ @@ -242,6 +245,7 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro setState: StateSetter; core: Pick; dateRange: DateRange; + dimensionGroups: VisualizationDimensionGroupConfig[]; }; export type DatasourceDimensionTriggerProps = DatasourceDimensionProps & { From 66f7f9c306b6063622f3825ffef4c563970e6f36 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 9 Nov 2020 15:26:54 +0000 Subject: [PATCH 66/81] fix(NA): missing change from KIBANA_PATH_CONF to KBN_PATH_CONF on bin script (#81500) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/dev/build/tasks/bin/scripts/kibana | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/bin/scripts/kibana b/src/dev/build/tasks/bin/scripts/kibana index c606436c7b83f3..a4fc5385500b58 100755 --- a/src/dev/build/tasks/bin/scripts/kibana +++ b/src/dev/build/tasks/bin/scripts/kibana @@ -14,7 +14,7 @@ while [ -h "$SCRIPT" ] ; do done DIR="$(dirname "${SCRIPT}")/.." -CONFIG_DIR=${KIBANA_PATH_CONF:-"$DIR/config"} +CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} NODE="${DIR}/node/bin/node" test -x "$NODE" if [ ! -x "$NODE" ]; then From d5736b10a900f07f154d24357ff3e58e0aca75f5 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 9 Nov 2020 10:46:19 -0500 Subject: [PATCH 67/81] [Enterprise Search] Log retention settings logic (#82364) --- .../log_retention/log_retention_logic.test.ts | 368 ++++++++++++++++++ .../log_retention/log_retention_logic.ts | 117 ++++++ .../settings/log_retention/types.ts | 42 ++ .../utils/convert_log_retention.test.ts | 64 +++ .../utils/convert_log_retention.ts | 45 +++ 5 files changed, 636 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts new file mode 100644 index 00000000000000..367c7b085123fc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts @@ -0,0 +1,368 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { resetContext } from 'kea'; + +import { mockHttpValues } from '../../../../__mocks__'; +jest.mock('../../../../shared/http', () => ({ + HttpLogic: { values: mockHttpValues }, +})); +const { http } = mockHttpValues; + +jest.mock('../../../../shared/flash_messages', () => ({ + flashAPIErrors: jest.fn(), +})); +import { flashAPIErrors } from '../../../../shared/flash_messages'; + +import { ELogRetentionOptions } from './types'; +import { LogRetentionLogic } from './log_retention_logic'; + +describe('LogRetentionLogic', () => { + const TYPICAL_SERVER_LOG_RETENTION = { + analytics: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + api: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + }; + + const TYPICAL_CLIENT_LOG_RETENTION = { + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + }; + + const DEFAULT_VALUES = { + logRetention: null, + openedModal: null, + isLogRetentionUpdating: false, + }; + + const mount = (defaults?: object) => { + if (!defaults) { + resetContext({}); + } else { + resetContext({ + defaults: { + enterprise_search: { + app_search: { + log_retention_logic: { + ...defaults, + }, + }, + }, + }, + }); + } + LogRetentionLogic.mount(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(LogRetentionLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('setOpenedModal', () => { + describe('openedModal', () => { + it('should be set to the provided value', () => { + mount(); + + LogRetentionLogic.actions.setOpenedModal(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + openedModal: ELogRetentionOptions.Analytics, + }); + }); + }); + }); + + describe('closeModals', () => { + describe('openedModal', () => { + it('resets openedModal to null', () => { + mount({ + openedModal: 'analytics', + }); + + LogRetentionLogic.actions.closeModals(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + openedModal: null, + }); + }); + }); + + describe('isLogRetentionUpdating', () => { + it('resets isLogRetentionUpdating to false', () => { + mount({ + isLogRetentionUpdating: true, + }); + + LogRetentionLogic.actions.closeModals(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: false, + }); + }); + }); + }); + + describe('clearLogRetentionUpdating', () => { + describe('isLogRetentionUpdating', () => { + it('resets isLogRetentionUpdating to false', () => { + mount({ + isLogRetentionUpdating: true, + }); + + LogRetentionLogic.actions.clearLogRetentionUpdating(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: false, + }); + }); + }); + }); + + describe('updateLogRetention', () => { + describe('logRetention', () => { + it('updates the logRetention values that are passed', () => { + mount({ + logRetention: {}, + }); + + LogRetentionLogic.actions.updateLogRetention({ + api: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + }); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + logRetention: { + api: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + }, + }); + }); + }); + }); + + describe('saveLogRetention', () => { + beforeEach(() => { + mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); + }); + + describe('openedModal', () => { + it('should be reset to null', () => { + mount({ + openedModal: ELogRetentionOptions.Analytics, + }); + + LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + openedModal: null, + }); + }); + }); + + it('will call an API endpoint and update log retention', async () => { + jest.spyOn(LogRetentionLogic.actions, 'updateLogRetention'); + const promise = Promise.resolve(TYPICAL_SERVER_LOG_RETENTION); + http.put.mockReturnValue(promise); + + LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + + expect(http.put).toHaveBeenCalledWith('/api/app_search/log_settings', { + body: JSON.stringify({ + analytics: { + enabled: true, + }, + }), + }); + + await promise; + expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( + TYPICAL_CLIENT_LOG_RETENTION + ); + + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + + it('handles errors', async () => { + const promise = Promise.reject('An error occured'); + http.put.mockReturnValue(promise); + + LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + }); + + describe('toggleLogRetention', () => { + describe('isLogRetentionUpdating', () => { + it('sets isLogRetentionUpdating to true', () => { + mount({ + isLogRetentionUpdating: false, + }); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: true, + }); + }); + }); + + it('will call setOpenedModal if already enabled', () => { + mount({ + logRetention: { + [ELogRetentionOptions.Analytics]: { + enabled: true, + }, + }, + }); + jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.actions.setOpenedModal).toHaveBeenCalledWith( + ELogRetentionOptions.Analytics + ); + }); + }); + + describe('fetchLogRetention', () => { + describe('isLogRetentionUpdating', () => { + it('sets isLogRetentionUpdating to true', () => { + mount({ + isLogRetentionUpdating: false, + }); + + LogRetentionLogic.actions.fetchLogRetention(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: true, + }); + }); + }); + + it('will call an API endpoint and update log retention', async () => { + mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); + jest + .spyOn(LogRetentionLogic.actions, 'updateLogRetention') + .mockImplementationOnce(() => {}); + + const promise = Promise.resolve(TYPICAL_SERVER_LOG_RETENTION); + http.get.mockReturnValue(promise); + + LogRetentionLogic.actions.fetchLogRetention(); + + expect(http.get).toHaveBeenCalledWith('/api/app_search/log_settings'); + await promise; + expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( + TYPICAL_CLIENT_LOG_RETENTION + ); + + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + + it('handles errors', async () => { + mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); + const promise = Promise.reject('An error occured'); + http.get.mockReturnValue(promise); + + LogRetentionLogic.actions.fetchLogRetention(); + + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + }); + + it('will call saveLogRetention if NOT already enabled', () => { + mount({ + logRetention: { + [ELogRetentionOptions.Analytics]: { + enabled: false, + }, + }, + }); + jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( + ELogRetentionOptions.Analytics, + true + ); + }); + + it('will do nothing if logRetention option is not yet set', () => { + mount({ + logRetention: {}, + }); + jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); + jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.API); + + expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); + expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts new file mode 100644 index 00000000000000..28830f2edb1d99 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { kea, MakeLogicType } from 'kea'; + +import { ELogRetentionOptions, ILogRetention, ILogRetentionServer } from './types'; +import { HttpLogic } from '../../../../shared/http'; +import { flashAPIErrors } from '../../../../shared/flash_messages'; +import { convertLogRetentionFromServerToClient } from './utils/convert_log_retention'; + +interface ILogRetentionActions { + clearLogRetentionUpdating(): { value: boolean }; + closeModals(): { value: boolean }; + fetchLogRetention(): { value: boolean }; + saveLogRetention( + option: ELogRetentionOptions, + enabled: boolean + ): { option: ELogRetentionOptions; enabled: boolean }; + setOpenedModal(option: ELogRetentionOptions): { option: ELogRetentionOptions }; + toggleLogRetention(option: ELogRetentionOptions): { option: ELogRetentionOptions }; + updateLogRetention(logRetention: ILogRetention): { logRetention: ILogRetention }; +} + +interface ILogRetentionValues { + logRetention: ILogRetention | null; + isLogRetentionUpdating: boolean; + openedModal: ELogRetentionOptions | null; +} + +export const LogRetentionLogic = kea>({ + path: ['enterprise_search', 'app_search', 'log_retention_logic'], + actions: () => ({ + clearLogRetentionUpdating: true, + closeModals: true, + fetchLogRetention: true, + saveLogRetention: (option, enabled) => ({ enabled, option }), + setOpenedModal: (option) => ({ option }), + toggleLogRetention: (option) => ({ option }), + updateLogRetention: (logRetention) => ({ logRetention }), + }), + reducers: () => ({ + logRetention: [ + null, + { + updateLogRetention: (_, { logRetention }) => logRetention, + }, + ], + isLogRetentionUpdating: [ + false, + { + clearLogRetentionUpdating: () => false, + closeModals: () => false, + fetchLogRetention: () => true, + toggleLogRetention: () => true, + }, + ], + openedModal: [ + null, + { + closeModals: () => null, + saveLogRetention: () => null, + setOpenedModal: (_, { option }) => option, + }, + ], + }), + listeners: ({ actions, values }) => ({ + fetchLogRetention: async () => { + try { + const { http } = HttpLogic.values; + const response = await http.get('/api/app_search/log_settings'); + actions.updateLogRetention( + convertLogRetentionFromServerToClient(response as ILogRetentionServer) + ); + } catch (e) { + flashAPIErrors(e); + } finally { + actions.clearLogRetentionUpdating(); + } + }, + saveLogRetention: async ({ enabled, option }) => { + const updateData = { [option]: { enabled } }; + + try { + const { http } = HttpLogic.values; + const response = await http.put('/api/app_search/log_settings', { + body: JSON.stringify(updateData), + }); + actions.updateLogRetention( + convertLogRetentionFromServerToClient(response as ILogRetentionServer) + ); + } catch (e) { + flashAPIErrors(e); + } finally { + actions.clearLogRetentionUpdating(); + } + }, + toggleLogRetention: ({ option }) => { + const logRetention = values.logRetention?.[option]; + + // If the user has found a way to call this before we've retrieved + // log retention settings from the server, short circuit this and return early + if (!logRetention) { + return; + } + + const optionIsAlreadyEnabled = logRetention.enabled; + if (optionIsAlreadyEnabled) { + actions.setOpenedModal(option); + } else { + actions.saveLogRetention(option, true); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts new file mode 100644 index 00000000000000..7d4f30f88f38f9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.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. + */ + +export enum ELogRetentionOptions { + Analytics = 'analytics', + API = 'api', +} + +export interface ILogRetention { + [ELogRetentionOptions.Analytics]: ILogRetentionSettings; + [ELogRetentionOptions.API]: ILogRetentionSettings; +} + +export interface ILogRetentionPolicy { + isDefault: boolean; + minAgeDays: number | null; +} + +export interface ILogRetentionSettings { + disabledAt?: string | null; + enabled?: boolean; + retentionPolicy?: ILogRetentionPolicy | null; +} + +export interface ILogRetentionServer { + [ELogRetentionOptions.Analytics]: ILogRetentionServerSettings; + [ELogRetentionOptions.API]: ILogRetentionServerSettings; +} + +export interface ILogRetentionServerPolicy { + is_default: boolean; + min_age_days: number | null; +} + +export interface ILogRetentionServerSettings { + disabled_at: string | null; + enabled: boolean; + retention_policy: ILogRetentionServerPolicy | null; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts new file mode 100644 index 00000000000000..b49b2afe50831b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { convertLogRetentionFromServerToClient } from './convert_log_retention'; + +describe('convertLogRetentionFromServerToClient', () => { + it('converts log retention from server to client', () => { + expect( + convertLogRetentionFromServerToClient({ + analytics: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + api: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + }) + ).toEqual({ + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + }); + }); + + it('handles null retention policies and null min_age_days', () => { + expect( + convertLogRetentionFromServerToClient({ + analytics: { + disabled_at: null, + enabled: true, + retention_policy: null, + }, + api: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: null }, + }, + }) + ).toEqual({ + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: null }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts new file mode 100644 index 00000000000000..1c0818fc286f2c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { + ELogRetentionOptions, + ILogRetention, + ILogRetentionPolicy, + ILogRetentionServer, + ILogRetentionServerPolicy, + ILogRetentionServerSettings, + ILogRetentionSettings, +} from '../types'; + +export const convertLogRetentionFromServerToClient = ( + logRetention: ILogRetentionServer +): ILogRetention => ({ + [ELogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( + logRetention[ELogRetentionOptions.Analytics] + ), + [ELogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( + logRetention[ELogRetentionOptions.API] + ), +}); + +const convertLogRetentionSettingsFromServerToClient = ({ + disabled_at: disabledAt, + enabled, + retention_policy: retentionPolicy, +}: ILogRetentionServerSettings): ILogRetentionSettings => ({ + disabledAt, + enabled, + retentionPolicy: + retentionPolicy === null ? null : convertLogRetentionPolicyFromServerToClient(retentionPolicy), +}); + +const convertLogRetentionPolicyFromServerToClient = ({ + min_age_days: minAgeDays, + is_default: isDefault, +}: ILogRetentionServerPolicy): ILogRetentionPolicy => ({ + isDefault, + minAgeDays, +}); From 0a71f2c45e340429941d021ab186a67851710a02 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 9 Nov 2020 09:50:57 -0600 Subject: [PATCH 68/81] [deb/rpm] On upgrade, restart kibana service (#82049) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../os_packages/package_scripts/post_install.sh | 13 +++++++++++++ src/dev/build/tasks/os_packages/run_fpm.ts | 3 ++- .../service_templates/systemd/etc/default/kibana | 10 +--------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 939226b565f791..728278dae746b4 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -1,6 +1,11 @@ #!/bin/sh set -e +# source the default env file +if [ -f "<%= envFile %>" ]; then + . "<%= envFile %>" +fi + export KBN_PATH_CONF=${KBN_PATH_CONF:-<%= configDir %>} set_chmod() { @@ -71,4 +76,12 @@ if [ "$IS_UPGRADE" = "true" ]; then if command -v systemctl >/dev/null; then systemctl daemon-reload fi + + if [ "$RESTART_ON_UPGRADE" = "true" ]; then + echo -n "Restarting kibana service..." + if command -v systemctl >/dev/null; then + systemctl restart kibana.service || true + fi + echo " OK" + fi fi diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index e5de760ea11d06..f16eaea1daa2f8 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -111,7 +111,8 @@ export async function runFpm( `dataDir=/var/lib/kibana`, '--template-value', `logDir=/var/log/kibana`, - + '--template-value', + `envFile=/etc/default/kibana`, // config and data directories are copied to /usr/share and /var/lib // below, so exclude them from the main package source located in // /usr/share/kibana/config. PATHS MUST BE RELATIVE, so drop the leading slash diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana index 9c9f58ded350bc..7d0c955964ae6b 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana @@ -1,13 +1,5 @@ user="kibana" group="kibana" -chroot="/" -chdir="/" -nice="" - - -# If this is set to 1, then when `stop` is called, if the process has -# not exited within a reasonable time, SIGKILL will be sent next. -# The default behavior is to simply log a message "program stop failed; still running" -KILL_ON_STOP_TIMEOUT=0 KBN_PATH_CONF="/etc/kibana" +RESTART_ON_UPGRADE="true" From d2d30e7f8056245f2e1974189c31202cba245fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 9 Nov 2020 16:54:48 +0100 Subject: [PATCH 69/81] Bump cypress dependencies (#82815) --- package.json | 10 +++++----- yarn.lock | 55 +++++++++++++++++++++++++++++----------------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index ade567c840da77..3500ed78ad7d13 100644 --- a/package.json +++ b/package.json @@ -345,8 +345,8 @@ "@babel/register": "^7.10.5", "@babel/traverse": "^7.11.5", "@babel/types": "^7.11.0", - "@cypress/snapshot": "^2.1.3", - "@cypress/webpack-preprocessor": "^5.4.1", + "@cypress/snapshot": "^2.1.7", + "@cypress/webpack-preprocessor": "^5.4.10", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", "@elastic/charts": "24.0.0", @@ -609,9 +609,9 @@ "cpy": "^8.1.1", "cronstrue": "^1.51.0", "css-loader": "^3.4.2", - "cypress": "^5.4.0", + "cypress": "^5.5.0", "cypress-cucumber-preprocessor": "^2.5.2", - "cypress-multi-reporters": "^1.2.3", + "cypress-multi-reporters": "^1.4.0", "d3": "3.5.17", "d3-cloud": "1.2.5", "d3-scale": "1.0.7", @@ -633,7 +633,7 @@ "eslint-module-utils": "2.5.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.4.0", - "eslint-plugin-cypress": "^2.8.1", + "eslint-plugin-cypress": "^2.11.2", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.19.1", "eslint-plugin-jest": "^24.0.2", diff --git a/yarn.lock b/yarn.lock index 0b429c96c18479..edbc186c22243e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1310,7 +1310,7 @@ tunnel-agent "^0.6.0" uuid "^3.3.2" -"@cypress/snapshot@^2.1.3": +"@cypress/snapshot@^2.1.7": version "2.1.7" resolved "https://registry.yarnpkg.com/@cypress/snapshot/-/snapshot-2.1.7.tgz#e7360eb628b062f28f03036382619ec72cfb1831" integrity sha512-f8AcfIg7wOOHSdBODlIwCJE/rG5Yb+kUY+WVTKynB2pLLoDy9nc8CtcazqX19q2Lh++nTJLNRihpbbWvk33mbg== @@ -1324,10 +1324,10 @@ snap-shot-compare "2.8.3" snap-shot-store "1.2.3" -"@cypress/webpack-preprocessor@^5.4.1": - version "5.4.6" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.6.tgz#667f8007cbe6ee219ce7e45a7f1400d3e2401032" - integrity sha512-78hWoTUUEncv647badwVbyszvmwI1r9GaY/xy7V0sz0VVC90ByuDkLpvN+J0VP6enthob4dIPXcm0f9Tb1UKQQ== +"@cypress/webpack-preprocessor@^5.4.10": + version "5.4.10" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.10.tgz#ed0a3ed495f5455899f7c567830b37477c3f26f5" + integrity sha512-6j809mAQcZsAbpIaQFlKwFQPKv1Y+ZyLr9bpW7d2utMGVll0W8Up4RPhIuAf4q9Tx+DOBQoiCpy/n0cRPxporw== dependencies: bluebird "^3.7.1" debug "^4.1.1" @@ -10711,23 +10711,23 @@ cypress-cucumber-preprocessor@^2.5.2: minimist "^1.2.0" through "^2.3.8" -cypress-multi-reporters@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.2.3.tgz#4ba39373631c6521d21931d73f6b0bafa1ccbf83" - integrity sha512-W3ItWsbSgMfsQFTuB89OXY5gyqLuM0O2lNEn+mcQAYeMs36TxVLAg3q+Hk0Om+NcWj8OLhM06lBQpnu9+i4gug== +cypress-multi-reporters@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.4.0.tgz#5f1d0484a20959cfe782f1bf65ad16c6ad804da7" + integrity sha512-CjpQduW43KVzY45hhKC/qf8MSebRpx6JyEz6py8F+0GrYS8rE5TZ8wXv9dPUs/PaT6w+dR8KIgLSMr967Om7iA== dependencies: debug "^4.1.1" - lodash "^4.17.11" + lodash "^4.17.15" cypress-promise@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA== -cypress@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.4.0.tgz#8833a76e91129add601f823d43c53eb512d162c5" - integrity sha512-BJR+u3DRSYMqaBS1a3l1rbh5AkMRHugbxcYYzkl+xYlO6dzcJVE8uAhghzVI/hxijCyBg1iuSe4TRp/g1PUg8Q== +cypress@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.5.0.tgz#1da0355794a43247f8a80cb7f505e83e1cf847cb" + integrity sha512-UHEiTca8AUTevbT2pWkHQlxoHtXmbq+h6Eiu/Mz8DqpNkF98zjTBLv/HFiKJUU5rQzp9EwSWtms33p5TWCJ8tQ== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5" @@ -10759,10 +10759,10 @@ cypress@^5.4.0: minimist "^1.2.5" moment "^2.27.0" ospath "^1.2.2" - pretty-bytes "^5.3.0" + pretty-bytes "^5.4.1" ramda "~0.26.1" request-progress "^3.0.0" - supports-color "^7.1.0" + supports-color "^7.2.0" tmp "~0.2.1" untildify "^4.0.0" url "^0.11.0" @@ -12704,10 +12704,10 @@ eslint-plugin-ban@^1.4.0: dependencies: requireindex "~1.2.0" -eslint-plugin-cypress@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.8.1.tgz#981a0f3658b40de430bcf05cabc96b396487c91f" - integrity sha512-jDpcP+MmjmqQO/x3bwIXgp4cl7Q66RYS5/IsuOQP4Qo2sEqE3DI8tTxBQ1EhnV5qEDd2Z2TYHR+5vYI6oCN4uw== +eslint-plugin-cypress@^2.11.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0" + integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA== dependencies: globals "^11.12.0" @@ -22404,10 +22404,10 @@ prettier@~2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -pretty-bytes@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" - integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== +pretty-bytes@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" + integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA== pretty-error@^2.1.1: version "2.1.1" @@ -26551,6 +26551,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" From b5e6d7c29df903c0809fa9ca2d37f8a5c117e852 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 9 Nov 2020 10:05:26 -0600 Subject: [PATCH 70/81] [deb/rpm] Remove /var prefix from tmpfiles.d (#82196) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- config/kibana.yml | 2 +- .../service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/kibana.yml b/config/kibana.yml index 58ae8b9346f51a..ce9fe28dae9160 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -80,7 +80,7 @@ #elasticsearch.logQueries: false # Specifies the path where Kibana creates the process ID file. -#pid.file: /var/run/kibana.pid +#pid.file: /run/kibana/kibana.pid # Enables you to specify a file where Kibana stores log output. #logging.dest: stdout diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf b/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf index b5422df52fe11a..fe033e30fbf58b 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf @@ -1 +1 @@ -d /var/run/kibana 0755 kibana kibana - - \ No newline at end of file +d /run/kibana 0755 kibana kibana - - \ No newline at end of file From 441a0d4ec9f713c864f742846084ce598222afa4 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 9 Nov 2020 11:07:04 -0500 Subject: [PATCH 71/81] [Fleet] Move ingestManager plugin to fleet (#82886) --- .github/CODEOWNERS | 4 ++-- .github/paths-labeller.yml | 2 +- docs/developer/plugin-list.asciidoc | 2 +- x-pack/.i18nrc.json | 2 +- .../plugins/{ingest_manager => fleet}/CHANGELOG.md | 0 x-pack/plugins/{ingest_manager => fleet}/README.md | 2 +- .../common/constants/agent.ts | 0 .../common/constants/agent_policy.ts | 0 .../common/constants/enrollment_api_key.ts | 0 .../common/constants/epm.ts | 0 .../common/constants/index.ts | 0 .../common/constants/output.ts | 0 .../common/constants/package_policy.ts | 0 .../common/constants/plugin.ts | 0 .../common/constants/routes.ts | 0 .../common/constants/settings.ts | 0 .../{ingest_manager => fleet}/common/index.ts | 0 .../{ingest_manager => fleet}/common/mocks.ts | 0 .../common/openapi/README.md | 0 .../common/openapi/bundled.json | 0 .../common/openapi/bundled.yaml | 0 .../common/openapi/components/README.md | 0 .../common/openapi/components/headers/kbn_xsrf.yaml | 0 .../common/openapi/components/parameters/kuery.yaml | 0 .../openapi/components/parameters/page_index.yaml | 0 .../openapi/components/parameters/page_size.yaml | 0 .../openapi/components/schemas/access_api_key.yaml | 0 .../common/openapi/components/schemas/agent.yaml | 0 .../openapi/components/schemas/agent_event.yaml | 0 .../openapi/components/schemas/agent_metadata.yaml | 0 .../openapi/components/schemas/agent_policy.yaml | 0 .../openapi/components/schemas/agent_status.yaml | 0 .../openapi/components/schemas/agent_type.yaml | 0 .../components/schemas/bulk_upgrade_agents.yaml | 0 .../components/schemas/enrollment_api_key.yaml | 0 .../openapi/components/schemas/new_agent_event.yaml | 0 .../components/schemas/new_agent_policy.yaml | 0 .../components/schemas/new_package_policy.yaml | 0 .../openapi/components/schemas/package_info.yaml | 0 .../openapi/components/schemas/package_policy.yaml | 0 .../openapi/components/schemas/search_result.yaml | 0 .../openapi/components/schemas/upgrade_agent.yaml | 0 .../common/openapi/entrypoint.yaml | 0 .../common/openapi/paths/README.md | 0 .../common/openapi/paths/agent_policies.yaml | 0 .../common/openapi/paths/agent_policies@delete.yaml | 0 .../paths/agent_policies@{agent_policy_id}.yaml | 0 .../agent_policies@{agent_policy_id}@copy.yaml | 0 .../common/openapi/paths/agent_status.yaml | 0 .../common/openapi/paths/agents.yaml | 0 .../common/openapi/paths/agents@bulk_upgrade.yaml | 0 .../common/openapi/paths/agents@enroll.yaml | 0 .../common/openapi/paths/agents@setup.yaml | 0 .../common/openapi/paths/agents@{agent_id}.yaml | 0 .../openapi/paths/agents@{agent_id}@acks.yaml | 0 .../openapi/paths/agents@{agent_id}@checkin.yaml | 0 .../openapi/paths/agents@{agent_id}@events.yaml | 0 .../openapi/paths/agents@{agent_id}@unenroll.yaml | 0 .../openapi/paths/agents@{agent_id}@upgrade.yaml | 0 .../common/openapi/paths/enrollment_api_keys.yaml | 0 .../openapi/paths/enrollment_api_keys@{key_id}.yaml | 0 .../common/openapi/paths/epm@categories.yaml | 0 .../common/openapi/paths/epm@packages.yaml | 0 .../common/openapi/paths/epm@packages@{pkgkey}.yaml | 0 .../common/openapi/paths/install@{os_type}.yaml | 0 .../common/openapi/paths/package_policies.yaml | 0 .../paths/package_policies@{package_policy_id}.yaml | 0 .../common/openapi/paths/setup.yaml | 0 .../common/services/agent_status.ts | 0 .../common/services/decode_cloud_id.test.ts | 0 .../common/services/decode_cloud_id.ts | 0 .../full_agent_policy_kibana_config.test.ts | 0 .../services/full_agent_policy_kibana_config.ts | 0 .../common/services/full_agent_policy_to_yaml.ts | 0 .../common/services/index.ts | 0 .../common/services/is_agent_upgradeable.test.ts | 0 .../common/services/is_agent_upgradeable.ts | 0 .../common/services/is_diff_path_protocol.test.ts | 0 .../common/services/is_diff_path_protocol.ts | 0 .../common/services/is_valid_namespace.test.ts | 0 .../common/services/is_valid_namespace.ts | 0 .../common/services/license.ts | 0 .../common/services/limited_package.ts | 0 .../package_policies_to_agent_inputs.test.ts | 0 .../services/package_policies_to_agent_inputs.ts | 0 .../services/package_to_package_policy.test.ts | 0 .../common/services/package_to_package_policy.ts | 0 .../common/services/routes.ts | 0 .../{ingest_manager => fleet}/common/types/index.ts | 0 .../common/types/models/agent.ts | 0 .../common/types/models/agent_policy.ts | 0 .../common/types/models/data_stream.ts | 0 .../common/types/models/enrollment_api_key.ts | 0 .../common/types/models/epm.ts | 0 .../common/types/models/index.ts | 0 .../common/types/models/output.ts | 0 .../common/types/models/package_policy.ts | 0 .../common/types/models/settings.ts | 0 .../common/types/rest_spec/agent.ts | 0 .../common/types/rest_spec/agent_policy.ts | 0 .../common/types/rest_spec/app.ts | 0 .../common/types/rest_spec/common.ts | 0 .../common/types/rest_spec/data_stream.ts | 0 .../common/types/rest_spec/enrollment_api_key.ts | 0 .../common/types/rest_spec/epm.ts | 0 .../common/types/rest_spec/fleet_setup.ts | 0 .../common/types/rest_spec/index.ts | 0 .../common/types/rest_spec/ingest_setup.ts | 0 .../common/types/rest_spec/install_script.ts | 0 .../common/types/rest_spec/output.ts | 0 .../common/types/rest_spec/package_policy.ts | 0 .../common/types/rest_spec/settings.ts | 0 .../dev_docs/actions_and_events.md | 0 .../dev_docs/api/agents_acks.md | 0 .../dev_docs/api/agents_checkin.md | 0 .../dev_docs/api/agents_enroll.md | 0 .../dev_docs/api/agents_list.md | 0 .../dev_docs/api/agents_unenroll.md | 0 .../{ingest_manager => fleet}/dev_docs/api/epm.md | 0 .../dev_docs/api_integration_tests.md | 0 .../{ingest_manager => fleet}/dev_docs/api_keys.md | 0 .../dev_docs/definitions.md | 0 .../{ingest_manager => fleet}/dev_docs/epm.md | 0 .../dev_docs/fleet_agent_communication.md | 0 .../dev_docs/fleet_agents_interactions_detailed.md | 0 .../dev_docs/indexing_strategy.md | 0 .../dev_docs/schema/agent_checkin.mml | 0 .../dev_docs/schema/agent_checkin.png | Bin .../dev_docs/schema/agent_enroll.mml | 0 .../dev_docs/schema/agent_enroll.png | Bin .../dev_docs/schema/saved_objects.mml | 0 .../dev_docs/schema/saved_objects.png | Bin .../{ingest_manager => fleet}/dev_docs/tracing.md | 0 .../plugins/{ingest_manager => fleet}/kibana.json | 0 .../plugins/{ingest_manager => fleet}/package.json | 0 .../applications/fleet}/components/alpha_flyout.tsx | 0 .../fleet}/components/alpha_messaging.tsx | 0 .../fleet}/components/context_menu_actions.tsx | 0 .../components/enrollment_instructions/index.tsx | 0 .../enrollment_instructions/manual/index.tsx | 0 .../public/applications/fleet}/components/error.tsx | 0 .../applications/fleet}/components/header.tsx | 0 .../fleet}/components/home_integration/index.tsx | 0 .../tutorial_directory_header_link.tsx | 0 .../home_integration/tutorial_directory_notice.tsx | 0 .../home_integration/tutorial_module_notice.tsx | 0 .../public/applications/fleet}/components/index.ts | 0 .../applications/fleet}/components/loading.tsx | 0 .../applications/fleet}/components/package_icon.tsx | 0 .../applications/fleet}/components/search_bar.tsx | 0 .../fleet}/components/settings_flyout.tsx | 0 .../public/applications/fleet}/constants/index.ts | 0 .../applications/fleet}/constants/page_paths.ts | 0 .../public/applications/fleet}/hooks/index.ts | 0 .../applications/fleet}/hooks/use_breadcrumbs.tsx | 0 .../applications/fleet}/hooks/use_capabilities.ts | 0 .../public/applications/fleet}/hooks/use_config.ts | 0 .../public/applications/fleet}/hooks/use_core.ts | 0 .../applications/fleet}/hooks/use_debounce.tsx | 0 .../public/applications/fleet}/hooks/use_deps.ts | 0 .../applications/fleet}/hooks/use_fleet_status.tsx | 0 .../public/applications/fleet}/hooks/use_input.ts | 0 .../fleet}/hooks/use_intra_app_state.tsx | 0 .../applications/fleet}/hooks/use_kibana_link.ts | 0 .../applications/fleet}/hooks/use_kibana_version.ts | 0 .../public/applications/fleet}/hooks/use_license.ts | 0 .../public/applications/fleet}/hooks/use_link.ts | 0 .../fleet}/hooks/use_package_icon_type.ts | 0 .../applications/fleet}/hooks/use_pagination.tsx | 0 .../fleet}/hooks/use_request/agent_policy.ts | 0 .../applications/fleet}/hooks/use_request/agents.ts | 0 .../applications/fleet}/hooks/use_request/app.ts | 0 .../fleet}/hooks/use_request/data_stream.ts | 0 .../fleet}/hooks/use_request/enrollment_api_keys.ts | 0 .../applications/fleet}/hooks/use_request/epm.ts | 0 .../applications/fleet}/hooks/use_request/index.ts | 0 .../fleet}/hooks/use_request/outputs.ts | 0 .../fleet}/hooks/use_request/package_policy.ts | 0 .../fleet}/hooks/use_request/settings.ts | 0 .../applications/fleet}/hooks/use_request/setup.ts | 0 .../fleet}/hooks/use_request/use_request.ts | 0 .../applications/fleet}/hooks/use_sorting.tsx | 0 .../applications/fleet}/hooks/use_url_params.ts | 0 .../public/applications/fleet}/index.tsx | 0 .../public/applications/fleet}/layouts/default.tsx | 0 .../public/applications/fleet}/layouts/index.tsx | 0 .../applications/fleet}/layouts/with_header.tsx | 0 .../applications/fleet}/layouts/without_header.tsx | 0 .../agent_policy/components/actions_menu.tsx | 2 +- .../components/agent_policy_copy_provider.tsx | 0 .../components/agent_policy_delete_provider.tsx | 0 .../agent_policy/components/agent_policy_form.tsx | 0 .../components/agent_policy_yaml_flyout.tsx | 0 .../components/confirm_deploy_modal.tsx | 0 .../components/danger_eui_context_menu_item.tsx | 0 .../sections/agent_policy/components/index.ts | 0 .../agent_policy/components/linked_agent_count.tsx | 0 .../components/package_policy_delete_provider.tsx | 0 .../components/custom_package_policy.tsx | 0 .../create_package_policy_page/components/index.ts | 0 .../components/layout.tsx | 0 .../components/package_policy_input_config.tsx | 0 .../components/package_policy_input_panel.tsx | 0 .../components/package_policy_input_stream.tsx | 0 .../components/package_policy_input_var_field.tsx | 0 .../create_package_policy_page/index.tsx | 0 .../services/has_invalid_but_required_var.test.ts | 0 .../services/has_invalid_but_required_var.ts | 0 .../create_package_policy_page/services/index.ts | 0 .../services/is_advanced_var.test.ts | 0 .../services/is_advanced_var.ts | 0 .../services/validate_package_policy.test.ts | 0 .../services/validate_package_policy.ts | 0 .../step_configure_package.tsx | 0 .../step_define_package_policy.tsx | 0 .../step_select_agent_policy.tsx | 0 .../step_select_package.tsx | 0 .../create_package_policy_page/types.ts | 0 .../agent_policy/details_page/components/index.ts | 0 .../components/package_policies/index.tsx | 0 .../package_policies/no_package_policies.tsx | 0 .../package_policies/package_policies_table.tsx | 0 .../details_page/components/settings/index.tsx | 0 .../agent_policy/details_page/hooks/index.ts | 0 .../details_page/hooks/use_agent_status.tsx | 0 .../agent_policy/details_page/hooks/use_config.tsx | 0 .../sections/agent_policy/details_page/index.tsx | 0 .../agent_policy/edit_package_policy_page/index.tsx | 0 .../fleet}/sections/agent_policy/index.tsx | 0 .../list_page/components/create_agent_policy.tsx | 0 .../agent_policy/list_page/components/index.ts | 0 .../sections/agent_policy/list_page/index.tsx | 0 .../agent_details_page/components/actions_menu.tsx | 0 .../agent_details_page/components/agent_details.tsx | 0 .../components/agent_events_table.tsx | 0 .../agents}/agent_details_page/components/helper.ts | 0 .../agents}/agent_details_page/components/index.ts | 0 .../components/metadata_flyout.tsx | 0 .../agent_details_page/components/metadata_form.tsx | 0 .../agent_details_page/components/type_labels.tsx | 0 .../agents}/agent_details_page/hooks/index.ts | 0 .../agents}/agent_details_page/hooks/use_agent.tsx | 0 .../sections/agents}/agent_details_page/index.tsx | 0 .../agent_list_page/components/bulk_actions.tsx | 0 .../sections/agents}/agent_list_page/index.tsx | 0 .../agent_policy_selection.tsx | 0 .../components/agent_enrollment_flyout/index.tsx | 0 .../managed_instructions.tsx | 0 .../standalone_instructions.tsx | 0 .../components/agent_enrollment_flyout/steps.tsx | 0 .../sections/agents}/components/agent_health.tsx | 0 .../components/agent_policy_package_badges.tsx | 0 .../agent_reassign_policy_flyout/index.tsx | 0 .../components/agent_unenroll_modal/index.tsx | 0 .../components/agent_upgrade_modal/index.tsx | 0 .../sections/agents}/components/donut_chart.tsx | 0 .../fleet/sections/agents}/components/index.tsx | 0 .../sections/agents}/components/list_layout.tsx | 0 .../fleet/sections/agents}/components/loading.tsx | 0 .../components/confirm_delete_modal.tsx | 0 .../components/new_enrollment_key_flyout.tsx | 0 .../agents}/enrollment_token_list_page/index.tsx | 0 .../error_pages/components/no_data_layout.tsx | 0 .../agents}/error_pages/enforce_security.tsx | 0 .../agents}/error_pages/invalid_license.tsx | 0 .../sections/agents}/error_pages/no_access.tsx | 0 .../applications/fleet/sections/agents}/index.tsx | 0 .../fleet/sections/agents}/setup_page/index.tsx | 0 .../fleet}/sections/data_stream/index.tsx | 0 .../components/data_stream_row_actions.tsx | 0 .../fleet}/sections/data_stream/list_page/index.tsx | 0 .../sections/epm/components/assets_facet_group.tsx | 0 .../fleet}/sections/epm/components/icon_panel.tsx | 0 .../fleet}/sections/epm/components/icons.tsx | 0 .../fleet}/sections/epm/components/package_card.tsx | 0 .../sections/epm/components/package_list_grid.tsx | 0 .../fleet}/sections/epm/components/release_badge.ts | 0 .../fleet}/sections/epm/components/requirements.tsx | 0 .../fleet}/sections/epm/components/version.tsx | 0 .../applications/fleet}/sections/epm/constants.tsx | 0 .../fleet}/sections/epm/hooks/index.tsx | 0 .../fleet}/sections/epm/hooks/use_links.tsx | 0 .../fleet}/sections/epm/hooks/use_local_search.tsx | 0 .../sections/epm/hooks/use_package_install.tsx | 0 .../applications/fleet}/sections/epm/index.tsx | 0 .../epm/screens/detail/confirm_package_install.tsx | 0 .../screens/detail/confirm_package_uninstall.tsx | 0 .../fleet}/sections/epm/screens/detail/content.tsx | 0 .../epm/screens/detail/content_collapse.tsx | 0 .../fleet}/sections/epm/screens/detail/index.tsx | 0 .../epm/screens/detail/installation_button.tsx | 0 .../fleet}/sections/epm/screens/detail/layout.tsx | 0 .../epm/screens/detail/markdown_renderers.tsx | 0 .../sections/epm/screens/detail/overview_panel.tsx | 0 .../epm/screens/detail/package_policies_panel.tsx | 0 .../fleet}/sections/epm/screens/detail/readme.tsx | 0 .../sections/epm/screens/detail/screenshots.tsx | 0 .../sections/epm/screens/detail/settings_panel.tsx | 0 .../sections/epm/screens/detail/side_nav_links.tsx | 0 .../sections/epm/screens/home/category_facets.tsx | 0 .../fleet}/sections/epm/screens/home/header.tsx | 0 .../fleet}/sections/epm/screens/home/index.tsx | 0 .../public/applications/fleet}/sections/index.tsx | 2 +- .../overview/components/agent_policy_section.tsx | 2 +- .../sections/overview/components/agent_section.tsx | 2 +- .../overview/components/datastream_section.tsx | 2 +- .../overview/components/integration_section.tsx | 2 +- .../sections/overview/components/overview_panel.tsx | 0 .../sections/overview/components/overview_stats.tsx | 0 .../applications/fleet}/sections/overview/index.tsx | 2 +- .../public/applications/fleet}/services/index.ts | 0 .../public/applications/fleet}/types/index.ts | 0 .../fleet}/types/intra_app_route_state.ts | 0 .../assets/illustration_integrations_darkmode.svg | 0 .../assets/illustration_integrations_lightmode.svg | 0 .../{ingest_manager => fleet}/public/index.ts | 8 ++++---- .../{ingest_manager => fleet}/public/plugin.ts | 12 ++++++------ .../scripts/dev_agent/index.js | 0 .../scripts/dev_agent/script.ts | 0 .../{ingest_manager => fleet}/scripts/readme.md | 0 .../server/collectors/agent_collectors.ts | 0 .../server/collectors/config_collectors.ts | 0 .../server/collectors/helpers.ts | 0 .../server/collectors/package_collectors.ts | 0 .../server/collectors/register.ts | 0 .../server/constants/index.ts | 0 .../server/errors/handlers.test.ts | 0 .../server/errors/handlers.ts | 0 .../server/errors/index.ts | 0 .../{ingest_manager => fleet}/server/index.ts | 0 .../server/integration_tests/router.test.ts | 4 ++-- .../{ingest_manager => fleet}/server/mocks.ts | 0 .../{ingest_manager => fleet}/server/plugin.ts | 0 .../server/routes/agent/acks_handlers.test.ts | 0 .../server/routes/agent/acks_handlers.ts | 0 .../server/routes/agent/actions_handlers.test.ts | 0 .../server/routes/agent/actions_handlers.ts | 0 .../server/routes/agent/handlers.ts | 0 .../server/routes/agent/index.ts | 0 .../server/routes/agent/unenroll_handler.ts | 0 .../server/routes/agent/upgrade_handler.ts | 0 .../server/routes/agent_policy/handlers.ts | 0 .../server/routes/agent_policy/index.ts | 0 .../server/routes/app/index.ts | 0 .../server/routes/data_streams/handlers.ts | 0 .../server/routes/data_streams/index.ts | 0 .../server/routes/enrollment_api_key/handler.ts | 0 .../server/routes/enrollment_api_key/index.ts | 0 .../server/routes/epm/handlers.ts | 0 .../server/routes/epm/index.ts | 0 .../server/routes/index.ts | 0 .../server/routes/install_script/index.ts | 0 .../server/routes/limited_concurrency.test.ts | 0 .../server/routes/limited_concurrency.ts | 0 .../server/routes/output/handler.ts | 0 .../server/routes/output/index.ts | 0 .../server/routes/package_policy/handlers.test.ts | 0 .../server/routes/package_policy/handlers.ts | 0 .../server/routes/package_policy/index.ts | 0 .../server/routes/settings/index.ts | 0 .../server/routes/setup/handlers.test.ts | 0 .../server/routes/setup/handlers.ts | 0 .../server/routes/setup/index.ts | 0 .../server/saved_objects/index.ts | 0 .../server/saved_objects/migrations/to_v7_10_0.ts | 0 .../server/services/agent_policy.test.ts | 0 .../server/services/agent_policy.ts | 0 .../server/services/agent_policy_update.ts | 0 .../server/services/agents/acks.test.ts | 0 .../server/services/agents/acks.ts | 0 .../server/services/agents/actions.test.ts | 0 .../server/services/agents/actions.ts | 0 .../server/services/agents/authenticate.test.ts | 0 .../server/services/agents/authenticate.ts | 0 .../server/services/agents/checkin/index.ts | 0 .../services/agents/checkin/rxjs_utils.test.ts | 0 .../server/services/agents/checkin/rxjs_utils.ts | 0 .../server/services/agents/checkin/state.ts | 0 .../agents/checkin/state_connected_agents.ts | 0 .../agents/checkin/state_new_actions.test.ts | 0 .../services/agents/checkin/state_new_actions.ts | 0 .../server/services/agents/crud.ts | 0 .../server/services/agents/enroll.test.ts | 0 .../server/services/agents/enroll.ts | 0 .../server/services/agents/events.ts | 0 .../server/services/agents/index.ts | 0 .../server/services/agents/reassign.ts | 0 .../server/services/agents/saved_objects.ts | 0 .../server/services/agents/setup.ts | 0 .../server/services/agents/status.test.ts | 0 .../server/services/agents/status.ts | 0 .../server/services/agents/unenroll.ts | 0 .../server/services/agents/update.ts | 0 .../server/services/agents/upgrade.ts | 0 .../server/services/api_keys/enrollment_api_key.ts | 0 .../server/services/api_keys/index.ts | 0 .../server/services/api_keys/security.ts | 0 .../server/services/app_context.ts | 0 .../server/services/config.ts | 0 .../server/services/epm/agent/agent.test.ts | 0 .../server/services/epm/agent/agent.ts | 0 .../server/services/epm/archive/cache.ts | 0 .../server/services/epm/archive/index.ts | 0 .../server/services/epm/archive/validation.ts | 0 .../services/epm/elasticsearch/ilm/install.ts | 0 .../server/services/epm/elasticsearch/index.test.ts | 0 .../server/services/epm/elasticsearch/index.ts | 0 .../epm/elasticsearch/ingest_pipeline/index.ts | 0 .../ingest_pipeline/ingest_pipelines.test.ts | 0 .../epm/elasticsearch/ingest_pipeline/install.ts | 0 .../epm/elasticsearch/ingest_pipeline/remove.ts | 0 .../tests/ingest_pipeline_template.json | 0 .../tests/ingest_pipelines/no_replacement.json | 0 .../tests/ingest_pipelines/no_replacement.yml | 0 .../tests/ingest_pipelines/real_input_beats.json | 0 .../tests/ingest_pipelines/real_input_beats.yml | 0 .../tests/ingest_pipelines/real_input_standard.json | 0 .../tests/ingest_pipelines/real_input_standard.yml | 0 .../tests/ingest_pipelines/real_output.json | 0 .../tests/ingest_pipelines/real_output.yml | 0 .../template/__snapshots__/template.test.ts.snap | 0 .../services/epm/elasticsearch/template/install.ts | 2 +- .../epm/elasticsearch/template/template.test.ts | 0 .../services/epm/elasticsearch/template/template.ts | 0 .../services/epm/elasticsearch/transform/common.ts | 0 .../services/epm/elasticsearch/transform/install.ts | 0 .../epm/elasticsearch/transform/remove.test.ts | 0 .../services/epm/elasticsearch/transform/remove.ts | 0 .../epm/elasticsearch/transform/transform.test.ts | 0 .../epm/fields/__snapshots__/field.test.ts.snap | 0 .../server/services/epm/fields/field.test.ts | 0 .../server/services/epm/fields/field.ts | 0 .../server/services/epm/fields/tests/base.yml | 0 .../services/epm/fields/tests/coredns.logs.yml | 0 .../server/services/epm/fields/tests/system.yml | 0 .../server/services/epm/kibana/assets/install.ts | 0 .../__snapshots__/install.test.ts.snap | 0 .../epm/kibana/index_pattern/install.test.ts | 0 .../services/epm/kibana/index_pattern/install.ts | 0 .../epm/kibana/index_pattern/tests/coredns.logs.yml | 0 .../kibana/index_pattern/tests/nginx.access.ecs.yml | 0 .../kibana/index_pattern/tests/nginx.error.ecs.yml | 0 .../epm/kibana/index_pattern/tests/nginx.fields.yml | 0 .../epm/kibana/index_pattern/tests/test_data.ts | 0 .../services/epm/packages/_install_package.test.ts | 0 .../services/epm/packages/_install_package.ts | 0 .../server/services/epm/packages/assets.test.ts | 0 .../server/services/epm/packages/assets.ts | 0 .../services/epm/packages/bulk_install_packages.ts | 0 .../ensure_installed_default_packages.test.ts | 0 .../server/services/epm/packages/get.ts | 0 .../services/epm/packages/get_install_type.test.ts | 0 .../server/services/epm/packages/index.ts | 0 .../server/services/epm/packages/install.ts | 0 .../server/services/epm/packages/remove.ts | 0 .../server/services/epm/registry/extract.ts | 0 .../server/services/epm/registry/index.test.ts | 0 .../server/services/epm/registry/index.ts | 0 .../server/services/epm/registry/proxy.test.ts | 0 .../server/services/epm/registry/proxy.ts | 0 .../server/services/epm/registry/registry_url.ts | 0 .../server/services/epm/registry/requests.test.ts | 0 .../server/services/epm/registry/requests.ts | 0 .../server/services/epm/registry/streams.ts | 0 .../server/services/es_index_pattern.ts | 0 .../server/services/index.ts | 0 .../server/services/install_script/index.ts | 0 .../install_script/install_templates/linux.ts | 0 .../install_script/install_templates/macos.ts | 0 .../install_script/install_templates/types.ts | 0 .../server/services/license.ts | 0 .../server/services/output.ts | 0 .../server/services/package_policy.test.ts | 0 .../server/services/package_policy.ts | 0 .../server/services/saved_object.test.ts | 0 .../server/services/saved_object.ts | 0 .../server/services/settings.ts | 0 .../server/services/setup.test.ts | 0 .../server/services/setup.ts | 0 .../server/services/setup_utils.test.ts | 0 .../server/services/setup_utils.ts | 0 .../server/types/index.tsx | 0 .../server/types/models/agent.ts | 0 .../server/types/models/agent_policy.ts | 0 .../server/types/models/enrollment_api_key.ts | 0 .../server/types/models/index.ts | 0 .../server/types/models/output.ts | 0 .../server/types/models/package_policy.ts | 0 .../server/types/rest_spec/agent.ts | 0 .../server/types/rest_spec/agent_policy.ts | 0 .../server/types/rest_spec/common.ts | 0 .../server/types/rest_spec/enrollment_api_key.ts | 0 .../server/types/rest_spec/epm.ts | 0 .../server/types/rest_spec/index.ts | 0 .../server/types/rest_spec/install_script.ts | 0 .../server/types/rest_spec/output.ts | 0 .../server/types/rest_spec/package_policy.ts | 0 .../server/types/rest_spec/settings.ts | 0 .../server/types/rest_spec/setup.ts | 0 .../public/application/app_context.tsx | 2 +- .../public/application/mount_management_section.ts | 2 +- x-pack/plugins/index_management/public/types.ts | 2 +- x-pack/plugins/security_solution/README.md | 2 +- .../common/endpoint/generate_data.ts | 9 +++------ .../security_solution/common/endpoint/index_data.ts | 2 +- .../endpoint/policy/migrations/to_v7_11.0.test.ts | 2 +- .../common/endpoint/policy/migrations/to_v7_11.0.ts | 2 +- .../common/endpoint/types/index.ts | 2 +- .../security_solution/public/app/home/setup.tsx | 2 +- .../public/common/hooks/endpoint/upgrade.ts | 2 +- .../common/mock/endpoint/dependencies_start_mock.ts | 5 +---- .../management/pages/endpoint_hosts/store/action.ts | 2 +- .../pages/endpoint_hosts/store/middleware.ts | 2 +- .../store/mock_endpoint_result_list.ts | 2 +- .../public/management/pages/endpoint_hosts/types.ts | 2 +- .../view/details/endpoint_details.tsx | 2 +- .../management/pages/endpoint_hosts/view/index.tsx | 2 +- .../pages/policy/store/policy_details/action.ts | 2 +- .../pages/policy/store/policy_list/action.ts | 5 +---- .../pages/policy/store/policy_list/index.test.ts | 2 +- .../pages/policy/store/policy_list/middleware.ts | 2 +- .../store/policy_list/services/ingest.test.ts | 2 +- .../policy/store/policy_list/services/ingest.ts | 2 +- .../policy/store/policy_list/test_mock_utils.ts | 2 +- .../public/management/pages/policy/types.ts | 2 +- .../configure_package_policy.tsx | 2 +- .../management/pages/policy/view/policy_list.tsx | 2 +- x-pack/plugins/security_solution/public/types.ts | 2 +- .../scripts/endpoint/resolver_generator_script.ts | 4 ++-- .../endpoint/endpoint_app_context_services.ts | 8 ++------ .../server/endpoint/ingest_integration.test.ts | 2 +- .../server/endpoint/ingest_integration.ts | 4 ++-- .../server/endpoint/lib/artifacts/mocks.ts | 4 ++-- .../security_solution/server/endpoint/mocks.ts | 4 ++-- .../routes/artifacts/download_exception_list.ts | 2 +- .../server/endpoint/routes/metadata/handlers.ts | 2 +- .../endpoint/routes/metadata/metadata.test.ts | 4 ++-- .../endpoint/routes/metadata/metadata_v1.test.ts | 4 ++-- .../routes/metadata/support/agent_status.test.ts | 6 +++--- .../routes/metadata/support/agent_status.ts | 6 +++--- .../routes/metadata/support/unenroll.test.ts | 4 ++-- .../endpoint/routes/metadata/support/unenroll.ts | 4 ++-- .../manifest_manager/manifest_manager.mock.ts | 4 ++-- .../manifest_manager/manifest_manager.test.ts | 2 +- .../artifacts/manifest_manager/manifest_manager.ts | 2 +- .../server/lib/hosts/elasticsearch_adapter.test.ts | 4 ++-- x-pack/plugins/security_solution/server/plugin.ts | 2 +- .../server/usage/endpoints/endpoint.mocks.ts | 6 +++--- .../server/usage/endpoints/endpoint.test.ts | 4 ++-- .../server/usage/endpoints/fleet_saved_objects.ts | 6 +++--- .../server/usage/endpoints/index.ts | 4 ++-- x-pack/test/common/services/ingest_manager.ts | 2 +- .../apis/epm/bulk_upgrade.ts | 2 +- .../apis/epm/install_remove_assets.ts | 2 +- .../apis/epm/install_update.ts | 2 +- .../apis/epm/package_install_complete.ts | 2 +- .../apis/epm/setup.ts | 2 +- .../apis/epm/template.ts | 2 +- .../apis/fleet/agents/upgrade.ts | 2 +- .../apps/endpoint/index.ts | 2 +- .../services/endpoint_policy.ts | 2 +- .../apis/index.ts | 2 +- 562 files changed, 112 insertions(+), 125 deletions(-) rename x-pack/plugins/{ingest_manager => fleet}/CHANGELOG.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/README.md (97%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/plugin.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/routes.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/mocks.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/README.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/bundled.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/bundled.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/README.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/headers/kbn_xsrf.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/parameters/kuery.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/parameters/page_index.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/parameters/page_size.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/access_api_key.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_event.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_metadata.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_status.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_type.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/bulk_upgrade_agents.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/enrollment_api_key.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/new_agent_event.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/new_agent_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/new_package_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/package_info.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/package_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/search_result.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/upgrade_agent.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/entrypoint.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/README.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies@delete.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies@{agent_policy_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_status.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@bulk_upgrade.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@enroll.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@setup.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@acks.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@checkin.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@events.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@unenroll.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@upgrade.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/enrollment_api_keys.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/enrollment_api_keys@{key_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/epm@categories.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/epm@packages.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/epm@packages@{pkgkey}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/install@{os_type}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/package_policies.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/package_policies@{package_policy_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/setup.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/agent_status.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/decode_cloud_id.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/decode_cloud_id.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/full_agent_policy_kibana_config.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/full_agent_policy_kibana_config.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/full_agent_policy_to_yaml.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_agent_upgradeable.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_agent_upgradeable.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_diff_path_protocol.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_diff_path_protocol.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_valid_namespace.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_valid_namespace.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/license.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/limited_package.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_policies_to_agent_inputs.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_policies_to_agent_inputs.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_to_package_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_to_package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/routes.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/data_stream.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/app.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/common.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/data_stream.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/fleet_setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/ingest_setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/install_script.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/actions_and_events.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_acks.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_checkin.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_enroll.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_list.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_unenroll.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/epm.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api_integration_tests.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api_keys.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/definitions.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/epm.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/fleet_agent_communication.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/fleet_agents_interactions_detailed.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/indexing_strategy.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_checkin.mml (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_checkin.png (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_enroll.mml (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_enroll.png (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/saved_objects.mml (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/saved_objects.png (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/tracing.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/kibana.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/package.json (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/alpha_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/alpha_messaging.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/context_menu_actions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/enrollment_instructions/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/enrollment_instructions/manual/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/error.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/tutorial_directory_header_link.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/tutorial_directory_notice.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/tutorial_module_notice.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/loading.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/package_icon.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/search_bar.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/settings_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/constants/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/constants/page_paths.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_breadcrumbs.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_capabilities.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_config.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_core.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_debounce.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_deps.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_fleet_status.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_input.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_intra_app_state.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_kibana_link.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_kibana_version.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_license.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_link.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_package_icon_type.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_pagination.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/agents.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/app.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/data_stream.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/enrollment_api_keys.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/epm.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/outputs.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/settings.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/setup.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/use_request.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_sorting.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_url_params.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/default.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/with_header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/without_header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/actions_menu.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_copy_provider.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_delete_provider.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_form.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_yaml_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/confirm_deploy_modal.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/danger_eui_context_menu_item.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/linked_agent_count.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/package_policy_delete_provider.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_configure_package.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_select_package.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/types.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/package_policies/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/settings/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/hooks/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/hooks/use_agent_status.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/hooks/use_config.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/edit_package_policy_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/list_page/components/create_agent_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/list_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/actions_menu.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/agent_details.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/agent_events_table.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/helper.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/metadata_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/metadata_form.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/type_labels.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/hooks/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/hooks/use_agent.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_list_page/components/bulk_actions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/agent_policy_selection.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/managed_instructions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/standalone_instructions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/steps.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_health.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_policy_package_badges.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_reassign_policy_flyout/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_unenroll_modal/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_upgrade_modal/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/donut_chart.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/list_layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/loading.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/enrollment_token_list_page/components/confirm_delete_modal.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/enrollment_token_list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/components/no_data_layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/enforce_security.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/invalid_license.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/no_access.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/setup_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/data_stream/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/data_stream/list_page/components/data_stream_row_actions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/data_stream/list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/assets_facet_group.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/icon_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/icons.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/package_card.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/package_list_grid.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/release_badge.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/requirements.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/version.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/constants.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/use_links.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/use_local_search.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/use_package_install.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/confirm_package_install.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/confirm_package_uninstall.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/content.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/content_collapse.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/installation_button.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/markdown_renderers.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/overview_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/package_policies_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/readme.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/screenshots.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/settings_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/side_nav_links.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/home/category_facets.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/home/header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/home/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/index.tsx (93%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/agent_policy_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/agent_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/datastream_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/integration_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/overview_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/overview_stats.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/index.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/services/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/types/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/types/intra_app_route_state.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/public/assets/illustration_integrations_darkmode.svg (100%) rename x-pack/plugins/{ingest_manager => fleet}/public/assets/illustration_integrations_lightmode.svg (100%) rename x-pack/plugins/{ingest_manager => fleet}/public/index.ts (65%) rename x-pack/plugins/{ingest_manager => fleet}/public/plugin.ts (91%) rename x-pack/plugins/{ingest_manager => fleet}/scripts/dev_agent/index.js (100%) rename x-pack/plugins/{ingest_manager => fleet}/scripts/dev_agent/script.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/scripts/readme.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/agent_collectors.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/config_collectors.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/helpers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/package_collectors.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/register.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/constants/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/errors/handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/errors/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/errors/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/integration_tests/router.test.ts (97%) rename x-pack/plugins/{ingest_manager => fleet}/server/mocks.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/plugin.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/acks_handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/acks_handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/actions_handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/actions_handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/unenroll_handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/upgrade_handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent_policy/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent_policy/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/app/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/data_streams/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/data_streams/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/enrollment_api_key/handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/enrollment_api_key/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/epm/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/epm/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/install_script/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/limited_concurrency.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/limited_concurrency.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/output/handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/output/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/package_policy/handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/package_policy/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/package_policy/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/settings/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/setup/handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/setup/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/setup/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/saved_objects/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/saved_objects/migrations/to_v7_10_0.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agent_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agent_policy_update.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/acks.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/acks.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/actions.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/actions.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/authenticate.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/authenticate.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/rxjs_utils.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/rxjs_utils.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state_connected_agents.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state_new_actions.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state_new_actions.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/crud.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/enroll.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/enroll.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/events.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/reassign.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/saved_objects.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/status.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/status.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/unenroll.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/update.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/upgrade.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/api_keys/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/api_keys/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/api_keys/security.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/app_context.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/config.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/agent/agent.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/agent/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/archive/cache.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/archive/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/archive/validation.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ilm/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/index.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/remove.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/install.ts (98%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/template.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/template.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/common.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/remove.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/remove.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/transform.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/__snapshots__/field.test.ts.snap (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/field.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/field.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/tests/base.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/tests/coredns.logs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/tests/system.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/assets/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/install.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/test_data.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/_install_package.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/_install_package.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/assets.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/assets.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/bulk_install_packages.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/ensure_installed_default_packages.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/get.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/get_install_type.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/remove.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/extract.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/index.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/proxy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/proxy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/registry_url.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/requests.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/requests.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/streams.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/es_index_pattern.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/install_templates/linux.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/install_templates/macos.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/install_templates/types.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/license.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/package_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/saved_object.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/saved_object.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup_utils.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup_utils.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/index.tsx (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/common.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/install_script.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/setup.ts (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 96670b5d5107b0..2d70f6a97eed23 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -136,7 +136,7 @@ # Observability UIs /x-pack/plugins/infra/ @elastic/logs-metrics-ui -/x-pack/plugins/ingest_manager/ @elastic/ingest-management +/x-pack/plugins/fleet/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/observability-ui /x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/uptime @elastic/uptime @@ -380,7 +380,7 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Observability design /x-pack/plugins/apm/**/*.scss @elastic/observability-design /x-pack/plugins/infra/**/*.scss @elastic/observability-design -/x-pack/plugins/ingest_manager/**/*.scss @elastic/observability-design +/x-pack/plugins/fleet/**/*.scss @elastic/observability-design /x-pack/plugins/observability/**/*.scss @elastic/observability-design /x-pack/plugins/monitoring/**/*.scss @elastic/observability-design diff --git a/.github/paths-labeller.yml b/.github/paths-labeller.yml index 2e8529b4a77046..bd8427ea18d69b 100644 --- a/.github/paths-labeller.yml +++ b/.github/paths-labeller.yml @@ -11,7 +11,7 @@ - "Team:apm": - "x-pack/plugins/apm/**/*.*" - "Team:Ingest Management": - - "x-pack/plugins/ingest_manager/**/*.*" + - "x-pack/plugins/fleet/**/*.*" - "x-pack/test/api_integration/apis/fleet/**/*.*" - "x-pack/test/epm_api_integration/**/*.*" - "Team:uptime": diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 3c62c1fbca9825..e89b6d86361c7f 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -413,7 +413,7 @@ Index Management by running this series of requests in Console: the infrastructure monitoring use-case within Kibana. -|{kib-repo}blob/{branch}/x-pack/plugins/ingest_manager/README.md[ingestManager] +|{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[ingestManager] |Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 3b1e4faf80bce7..2be68b797ba5fd 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -28,7 +28,7 @@ "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", - "xpack.fleet": "plugins/ingest_manager", + "xpack.fleet": "plugins/fleet", "xpack.ingestPipelines": "plugins/ingest_pipelines", "xpack.lens": "plugins/lens", "xpack.licenseMgmt": "plugins/license_management", diff --git a/x-pack/plugins/ingest_manager/CHANGELOG.md b/x-pack/plugins/fleet/CHANGELOG.md similarity index 100% rename from x-pack/plugins/ingest_manager/CHANGELOG.md rename to x-pack/plugins/fleet/CHANGELOG.md diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/fleet/README.md similarity index 97% rename from x-pack/plugins/ingest_manager/README.md rename to x-pack/plugins/fleet/README.md index ade5985782c898..78ac2e3bdfdbec 100644 --- a/x-pack/plugins/ingest_manager/README.md +++ b/x-pack/plugins/fleet/README.md @@ -2,7 +2,7 @@ ## Plugin -- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/index.ts#L9-L27) +- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/common/types/index.ts#L9-L27) - Adding `xpack.fleet.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_POLICY_API_ROUTES` and `AGENT_POLICY_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) - Adding `--xpack.fleet.agents.enabled=false` will disable the Fleet API & UI - [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133) diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/fleet/common/constants/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/agent.ts rename to x-pack/plugins/fleet/common/constants/agent.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/agent_policy.ts rename to x-pack/plugins/fleet/common/constants/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts b/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts rename to x-pack/plugins/fleet/common/constants/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/epm.ts rename to x-pack/plugins/fleet/common/constants/epm.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/index.ts rename to x-pack/plugins/fleet/common/constants/index.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/output.ts b/x-pack/plugins/fleet/common/constants/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/output.ts rename to x-pack/plugins/fleet/common/constants/output.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/package_policy.ts b/x-pack/plugins/fleet/common/constants/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/package_policy.ts rename to x-pack/plugins/fleet/common/constants/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/plugin.ts b/x-pack/plugins/fleet/common/constants/plugin.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/plugin.ts rename to x-pack/plugins/fleet/common/constants/plugin.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/routes.ts rename to x-pack/plugins/fleet/common/constants/routes.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/settings.ts b/x-pack/plugins/fleet/common/constants/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/settings.ts rename to x-pack/plugins/fleet/common/constants/settings.ts diff --git a/x-pack/plugins/ingest_manager/common/index.ts b/x-pack/plugins/fleet/common/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/index.ts rename to x-pack/plugins/fleet/common/index.ts diff --git a/x-pack/plugins/ingest_manager/common/mocks.ts b/x-pack/plugins/fleet/common/mocks.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/mocks.ts rename to x-pack/plugins/fleet/common/mocks.ts diff --git a/x-pack/plugins/ingest_manager/common/openapi/README.md b/x-pack/plugins/fleet/common/openapi/README.md similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/README.md rename to x-pack/plugins/fleet/common/openapi/README.md diff --git a/x-pack/plugins/ingest_manager/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/bundled.json rename to x-pack/plugins/fleet/common/openapi/bundled.json diff --git a/x-pack/plugins/ingest_manager/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/bundled.yaml rename to x-pack/plugins/fleet/common/openapi/bundled.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/README.md b/x-pack/plugins/fleet/common/openapi/components/README.md similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/README.md rename to x-pack/plugins/fleet/common/openapi/components/README.md diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/headers/kbn_xsrf.yaml b/x-pack/plugins/fleet/common/openapi/components/headers/kbn_xsrf.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/headers/kbn_xsrf.yaml rename to x-pack/plugins/fleet/common/openapi/components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/parameters/kuery.yaml b/x-pack/plugins/fleet/common/openapi/components/parameters/kuery.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/parameters/kuery.yaml rename to x-pack/plugins/fleet/common/openapi/components/parameters/kuery.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_index.yaml b/x-pack/plugins/fleet/common/openapi/components/parameters/page_index.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_index.yaml rename to x-pack/plugins/fleet/common/openapi/components/parameters/page_index.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_size.yaml b/x-pack/plugins/fleet/common/openapi/components/parameters/page_size.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_size.yaml rename to x-pack/plugins/fleet/common/openapi/components/parameters/page_size.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/access_api_key.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/access_api_key.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/access_api_key.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/access_api_key.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_event.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_event.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_metadata.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_metadata.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_metadata.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_metadata.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_status.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_status.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_status.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_type.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_type.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_type.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_type.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/bulk_upgrade_agents.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/bulk_upgrade_agents.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/bulk_upgrade_agents.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/bulk_upgrade_agents.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/enrollment_api_key.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/enrollment_api_key.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_event.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_event.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_package_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_info.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/package_info.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_info.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/package_info.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/package_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/search_result.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/search_result.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/search_result.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/search_result.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/upgrade_agent.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/upgrade_agent.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/upgrade_agent.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/upgrade_agent.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/entrypoint.yaml rename to x-pack/plugins/fleet/common/openapi/entrypoint.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/README.md b/x-pack/plugins/fleet/common/openapi/paths/README.md similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/README.md rename to x-pack/plugins/fleet/common/openapi/paths/README.md diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@delete.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@delete.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_status.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@bulk_upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@bulk_upgrade.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@enroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@enroll.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@setup.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@acks.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@acks.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@acks.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@acks.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@checkin.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@checkin.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@checkin.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@checkin.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@events.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@events.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@events.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@events.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@unenroll.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@upgrade.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys.yaml rename to x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys@{key_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys@{key_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/epm@categories.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/epm@categories.yaml rename to x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages.yaml rename to x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages@{pkgkey}.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages@{pkgkey}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/install@{os_type}.yaml b/x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/install@{os_type}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/package_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/package_policies.yaml rename to x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/package_policies@{package_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/package_policies@{package_policy_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/setup.yaml rename to x-pack/plugins/fleet/common/openapi/paths/setup.yaml diff --git a/x-pack/plugins/ingest_manager/common/services/agent_status.ts b/x-pack/plugins/fleet/common/services/agent_status.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/agent_status.ts rename to x-pack/plugins/fleet/common/services/agent_status.ts diff --git a/x-pack/plugins/ingest_manager/common/services/decode_cloud_id.test.ts b/x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/decode_cloud_id.test.ts rename to x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/decode_cloud_id.ts b/x-pack/plugins/fleet/common/services/decode_cloud_id.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/decode_cloud_id.ts rename to x-pack/plugins/fleet/common/services/decode_cloud_id.ts diff --git a/x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.test.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.test.ts rename to x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.ts rename to x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.ts diff --git a/x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts rename to x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/index.ts rename to x-pack/plugins/fleet/common/services/index.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts rename to x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts rename to x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.ts b/x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.ts rename to x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.ts b/x-pack/plugins/fleet/common/services/is_diff_path_protocol.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.ts rename to x-pack/plugins/fleet/common/services/is_diff_path_protocol.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_valid_namespace.test.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_valid_namespace.test.ts rename to x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_valid_namespace.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_valid_namespace.ts rename to x-pack/plugins/fleet/common/services/is_valid_namespace.ts diff --git a/x-pack/plugins/ingest_manager/common/services/license.ts b/x-pack/plugins/fleet/common/services/license.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/license.ts rename to x-pack/plugins/fleet/common/services/license.ts diff --git a/x-pack/plugins/ingest_manager/common/services/limited_package.ts b/x-pack/plugins/fleet/common/services/limited_package.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/limited_package.ts rename to x-pack/plugins/fleet/common/services/limited_package.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts rename to x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts rename to x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts rename to x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts rename to x-pack/plugins/fleet/common/services/package_to_package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/routes.ts rename to x-pack/plugins/fleet/common/services/routes.ts diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/index.ts rename to x-pack/plugins/fleet/common/types/index.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/agent.ts rename to x-pack/plugins/fleet/common/types/models/agent.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts rename to x-pack/plugins/fleet/common/types/models/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/data_stream.ts b/x-pack/plugins/fleet/common/types/models/data_stream.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/data_stream.ts rename to x-pack/plugins/fleet/common/types/models/data_stream.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts rename to x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/epm.ts rename to x-pack/plugins/fleet/common/types/models/epm.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/index.ts b/x-pack/plugins/fleet/common/types/models/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/index.ts rename to x-pack/plugins/fleet/common/types/models/index.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/output.ts rename to x-pack/plugins/fleet/common/types/models/output.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/package_policy.ts rename to x-pack/plugins/fleet/common/types/models/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/settings.ts rename to x-pack/plugins/fleet/common/types/models/settings.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts rename to x-pack/plugins/fleet/common/types/rest_spec/agent.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts rename to x-pack/plugins/fleet/common/types/rest_spec/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/app.ts b/x-pack/plugins/fleet/common/types/rest_spec/app.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/app.ts rename to x-pack/plugins/fleet/common/types/rest_spec/app.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts b/x-pack/plugins/fleet/common/types/rest_spec/common.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts rename to x-pack/plugins/fleet/common/types/rest_spec/common.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/data_stream.ts b/x-pack/plugins/fleet/common/types/rest_spec/data_stream.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/data_stream.ts rename to x-pack/plugins/fleet/common/types/rest_spec/data_stream.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts rename to x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts rename to x-pack/plugins/fleet/common/types/rest_spec/epm.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts rename to x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts b/x-pack/plugins/fleet/common/types/rest_spec/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts rename to x-pack/plugins/fleet/common/types/rest_spec/index.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/ingest_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/ingest_setup.ts rename to x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/install_script.ts b/x-pack/plugins/fleet/common/types/rest_spec/install_script.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/install_script.ts rename to x-pack/plugins/fleet/common/types/rest_spec/install_script.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts rename to x-pack/plugins/fleet/common/types/rest_spec/output.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/common/types/rest_spec/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts rename to x-pack/plugins/fleet/common/types/rest_spec/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts b/x-pack/plugins/fleet/common/types/rest_spec/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts rename to x-pack/plugins/fleet/common/types/rest_spec/settings.ts diff --git a/x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md b/x-pack/plugins/fleet/dev_docs/actions_and_events.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md rename to x-pack/plugins/fleet/dev_docs/actions_and_events.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_acks.md b/x-pack/plugins/fleet/dev_docs/api/agents_acks.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_acks.md rename to x-pack/plugins/fleet/dev_docs/api/agents_acks.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_checkin.md b/x-pack/plugins/fleet/dev_docs/api/agents_checkin.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_checkin.md rename to x-pack/plugins/fleet/dev_docs/api/agents_checkin.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md b/x-pack/plugins/fleet/dev_docs/api/agents_enroll.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md rename to x-pack/plugins/fleet/dev_docs/api/agents_enroll.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md b/x-pack/plugins/fleet/dev_docs/api/agents_list.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md rename to x-pack/plugins/fleet/dev_docs/api/agents_list.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_unenroll.md b/x-pack/plugins/fleet/dev_docs/api/agents_unenroll.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_unenroll.md rename to x-pack/plugins/fleet/dev_docs/api/agents_unenroll.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/epm.md b/x-pack/plugins/fleet/dev_docs/api/epm.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/epm.md rename to x-pack/plugins/fleet/dev_docs/api/epm.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api_integration_tests.md b/x-pack/plugins/fleet/dev_docs/api_integration_tests.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api_integration_tests.md rename to x-pack/plugins/fleet/dev_docs/api_integration_tests.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api_keys.md b/x-pack/plugins/fleet/dev_docs/api_keys.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api_keys.md rename to x-pack/plugins/fleet/dev_docs/api_keys.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/definitions.md b/x-pack/plugins/fleet/dev_docs/definitions.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/definitions.md rename to x-pack/plugins/fleet/dev_docs/definitions.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/epm.md b/x-pack/plugins/fleet/dev_docs/epm.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/epm.md rename to x-pack/plugins/fleet/dev_docs/epm.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/fleet_agent_communication.md b/x-pack/plugins/fleet/dev_docs/fleet_agent_communication.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/fleet_agent_communication.md rename to x-pack/plugins/fleet/dev_docs/fleet_agent_communication.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md b/x-pack/plugins/fleet/dev_docs/fleet_agents_interactions_detailed.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md rename to x-pack/plugins/fleet/dev_docs/fleet_agents_interactions_detailed.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/indexing_strategy.md b/x-pack/plugins/fleet/dev_docs/indexing_strategy.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/indexing_strategy.md rename to x-pack/plugins/fleet/dev_docs/indexing_strategy.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml b/x-pack/plugins/fleet/dev_docs/schema/agent_checkin.mml similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml rename to x-pack/plugins/fleet/dev_docs/schema/agent_checkin.mml diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.png b/x-pack/plugins/fleet/dev_docs/schema/agent_checkin.png similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.png rename to x-pack/plugins/fleet/dev_docs/schema/agent_checkin.png diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.mml b/x-pack/plugins/fleet/dev_docs/schema/agent_enroll.mml similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.mml rename to x-pack/plugins/fleet/dev_docs/schema/agent_enroll.mml diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.png b/x-pack/plugins/fleet/dev_docs/schema/agent_enroll.png similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.png rename to x-pack/plugins/fleet/dev_docs/schema/agent_enroll.png diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml b/x-pack/plugins/fleet/dev_docs/schema/saved_objects.mml similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml rename to x-pack/plugins/fleet/dev_docs/schema/saved_objects.mml diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.png b/x-pack/plugins/fleet/dev_docs/schema/saved_objects.png similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.png rename to x-pack/plugins/fleet/dev_docs/schema/saved_objects.png diff --git a/x-pack/plugins/ingest_manager/dev_docs/tracing.md b/x-pack/plugins/fleet/dev_docs/tracing.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/tracing.md rename to x-pack/plugins/fleet/dev_docs/tracing.md diff --git a/x-pack/plugins/ingest_manager/kibana.json b/x-pack/plugins/fleet/kibana.json similarity index 100% rename from x-pack/plugins/ingest_manager/kibana.json rename to x-pack/plugins/fleet/kibana.json diff --git a/x-pack/plugins/ingest_manager/package.json b/x-pack/plugins/fleet/package.json similarity index 100% rename from x-pack/plugins/ingest_manager/package.json rename to x-pack/plugins/fleet/package.json diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/alpha_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/alpha_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_messaging.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/alpha_messaging.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_messaging.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/alpha_messaging.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/error.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/error.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/error.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/error.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_header_link.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_header_link.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_header_link.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_header_link.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_notice.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_notice.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_module_notice.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_module_notice.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/constants/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts rename to x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_core.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_core.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_debounce.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_debounce.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_fleet_status.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_fleet_status.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_intra_app_state.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_intra_app_state.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_link.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_link.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_link.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_link.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_version.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_version.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_version.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_version.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_license.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_license.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_link.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_link.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_package_icon_type.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_package_icon_type.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_package_icon_type.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_package_icon_type.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_pagination.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_pagination.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agents.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agents.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/app.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/app.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/app.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/app.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/data_stream.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/data_stream.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/data_stream.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/data_stream.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/enrollment_api_keys.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/enrollment_api_keys.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/enrollment_api_keys.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/enrollment_api_keys.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/epm.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/outputs.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/outputs.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/package_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/package_policy.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/settings.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/setup.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/setup.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/setup.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_sorting.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_sorting.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_url_params.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_url_params.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_url_params.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_url_params.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/without_header.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/without_header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/without_header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/without_header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/actions_menu.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx index 55872aee5c3d34..4ded4b7786c8fb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx @@ -9,7 +9,7 @@ import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; import { AgentPolicy } from '../../../types'; import { useCapabilities } from '../../../hooks'; import { ContextMenuActions } from '../../../components'; -import { AgentEnrollmentFlyout } from '../../fleet/components'; +import { AgentEnrollmentFlyout } from '../../agents/components'; import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout'; import { AgentPolicyCopyProvider } from './agent_policy_copy_provider'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_form.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/confirm_deploy_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/confirm_deploy_modal.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/danger_eui_context_menu_item.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/danger_eui_context_menu_item.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/danger_eui_context_menu_item.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/danger_eui_context_menu_item.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/linked_agent_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/linked_agent_count.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_package.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/types.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/types.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_agent_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_agent_status.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_config.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_config.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_config.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_details.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_details.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_events_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_events_table.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_events_table.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_events_table.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/helper.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/helper.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/helper.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/helper.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_form.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_form.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_form.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/type_labels.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/type_labels.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/type_labels.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/type_labels.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/use_agent.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/use_agent.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/use_agent.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/use_agent.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/agent_policy_selection.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/agent_policy_selection.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/steps.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_health.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_policy_package_badges.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_modal/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_upgrade_modal/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/donut_chart.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/donut_chart.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/donut_chart.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/donut_chart.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/list_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/list_layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/loading.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/loading.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/loading.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/loading.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/components/no_data_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/components/no_data_layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/components/no_data_layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/components/no_data_layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/enforce_security.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/enforce_security.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/enforce_security.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/enforce_security.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/invalid_license.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/invalid_license.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/invalid_license.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/invalid_license.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/no_access.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/no_access.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icons.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icons.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/release_badge.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/release_badge.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/release_badge.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/release_badge.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_install.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_install.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_uninstall.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_uninstall.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content_collapse.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content_collapse.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/installation_button.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/installation_button.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/markdown_renderers.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/markdown_renderers.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/package_policies_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/package_policies_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/readme.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/readme.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/screenshots.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/screenshots.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/side_nav_links.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/side_nav_links.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx similarity index 93% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx index 3a0f2c3ea8d58e..7642c1b3c8af28 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx @@ -7,6 +7,6 @@ export { IngestManagerOverview } from './overview'; export { EPMApp } from './epm'; export { AgentPolicyApp } from './agent_policy'; export { DataStreamApp } from './data_stream'; -export { FleetApp } from './fleet'; +export { FleetApp } from './agents'; export type Section = 'overview' | 'epm' | 'agent_policy' | 'fleet' | 'data_stream'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_policy_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx index c49f99cfe8a04a..6d26328cda4b95 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_policy_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx @@ -18,7 +18,7 @@ import { OverviewStats } from './overview_stats'; import { SO_SEARCH_LIMIT } from '../../../constants'; import { useLink, useGetPackagePolicies } from '../../../hooks'; import { AgentPolicy } from '../../../types'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; export const OverviewPolicySection: React.FC<{ agentPolicies: AgentPolicy[] }> = ({ agentPolicies, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx index 5fbdf62d138d45..89d869c97621e2 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx @@ -16,7 +16,7 @@ import { import { OverviewPanel } from './overview_panel'; import { OverviewStats } from './overview_stats'; import { useLink, useGetAgentStatus } from '../../../hooks'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; export const OverviewAgentSection = () => { const { getHref } = useLink(); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx index 3b645f7aa9d66c..58f84e86713853 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx @@ -16,7 +16,7 @@ import { import { OverviewPanel } from './overview_panel'; import { OverviewStats } from './overview_stats'; import { useLink, useGetDataStreams, useStartDeps } from '../../../hooks'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; export const OverviewDatastreamSection: React.FC = () => { const { getHref } = useLink(); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/integration_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/integration_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx index 3f13b65a160d8e..d69ec1f2aa1dce 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/integration_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx @@ -16,7 +16,7 @@ import { import { OverviewPanel } from './overview_panel'; import { OverviewStats } from './overview_stats'; import { useLink, useGetPackages } from '../../../hooks'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; import { installationStatuses } from '../../../../../../common/constants'; export const OverviewIntegrationSection: React.FC = () => { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_stats.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_stats.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx index c997caa90c6531..1254d5db04d19b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx @@ -17,7 +17,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { WithHeaderLayout } from '../../layouts'; import { useGetAgentPolicies, useBreadcrumbs } from '../../hooks'; -import { AgentEnrollmentFlyout } from '../fleet/components'; +import { AgentEnrollmentFlyout } from '../agents/components'; import { OverviewAgentSection } from './components/agent_section'; import { OverviewPolicySection } from './components/agent_policy_section'; import { OverviewIntegrationSection } from './components/integration_section'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/fleet/public/applications/fleet/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/services/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/types/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts b/x-pack/plugins/fleet/public/applications/fleet/types/intra_app_route_state.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts rename to x-pack/plugins/fleet/public/applications/fleet/types/intra_app_route_state.ts diff --git a/x-pack/plugins/ingest_manager/public/assets/illustration_integrations_darkmode.svg b/x-pack/plugins/fleet/public/assets/illustration_integrations_darkmode.svg similarity index 100% rename from x-pack/plugins/ingest_manager/public/assets/illustration_integrations_darkmode.svg rename to x-pack/plugins/fleet/public/assets/illustration_integrations_darkmode.svg diff --git a/x-pack/plugins/ingest_manager/public/assets/illustration_integrations_lightmode.svg b/x-pack/plugins/fleet/public/assets/illustration_integrations_lightmode.svg similarity index 100% rename from x-pack/plugins/ingest_manager/public/assets/illustration_integrations_lightmode.svg rename to x-pack/plugins/fleet/public/assets/illustration_integrations_lightmode.svg diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/fleet/public/index.ts similarity index 65% rename from x-pack/plugins/ingest_manager/public/index.ts rename to x-pack/plugins/fleet/public/index.ts index a8a810b68735ab..f974a8c3d3cc81 100644 --- a/x-pack/plugins/ingest_manager/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -16,9 +16,9 @@ export { CustomConfigurePackagePolicyContent, CustomConfigurePackagePolicyProps, registerPackagePolicyComponent, -} from './applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; +} from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; -export type { NewPackagePolicy } from './applications/ingest_manager/types'; -export * from './applications/ingest_manager/types/intra_app_route_state'; +export type { NewPackagePolicy } from './applications/fleet/types'; +export * from './applications/fleet/types/intra_app_route_state'; -export { pagePathGetters } from './applications/ingest_manager/constants'; +export { pagePathGetters } from './applications/fleet/constants'; diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts similarity index 91% rename from x-pack/plugins/ingest_manager/public/plugin.ts rename to x-pack/plugins/fleet/public/plugin.ts index 6847a39819e8ed..2e7cbb9cb86ab7 100644 --- a/x-pack/plugins/ingest_manager/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -19,18 +19,18 @@ import { } from '../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../licensing/public'; import { PLUGIN_ID, CheckPermissionsResponse, PostIngestSetupResponse } from '../common'; -import { BASE_PATH } from './applications/ingest_manager/constants'; +import { BASE_PATH } from './applications/fleet/constants'; import { IngestManagerConfigType } from '../common/types'; import { setupRouteService, appRoutesService } from '../common'; -import { licenseService } from './applications/ingest_manager/hooks/use_license'; -import { setHttpClient } from './applications/ingest_manager/hooks/use_request/use_request'; +import { licenseService } from './applications/fleet/hooks/use_license'; +import { setHttpClient } from './applications/fleet/hooks/use_request/use_request'; import { TutorialDirectoryNotice, TutorialDirectoryHeaderLink, TutorialModuleNotice, -} from './applications/ingest_manager/components/home_integration'; -import { registerPackagePolicyComponent } from './applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; +} from './applications/fleet/components/home_integration'; +import { registerPackagePolicyComponent } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; export { IngestManagerConfigType } from '../common/types'; @@ -91,7 +91,7 @@ export class IngestManagerPlugin IngestManagerStartDeps, IngestManagerStart ]; - const { renderApp, teardownIngestManager } = await import('./applications/ingest_manager'); + const { renderApp, teardownIngestManager } = await import('./applications/fleet/'); const unmount = renderApp(coreStart, params, deps, startDeps, config, kibanaVersion); return () => { diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/index.js b/x-pack/plugins/fleet/scripts/dev_agent/index.js similarity index 100% rename from x-pack/plugins/ingest_manager/scripts/dev_agent/index.js rename to x-pack/plugins/fleet/scripts/dev_agent/index.js diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts b/x-pack/plugins/fleet/scripts/dev_agent/script.ts similarity index 100% rename from x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts rename to x-pack/plugins/fleet/scripts/dev_agent/script.ts diff --git a/x-pack/plugins/ingest_manager/scripts/readme.md b/x-pack/plugins/fleet/scripts/readme.md similarity index 100% rename from x-pack/plugins/ingest_manager/scripts/readme.md rename to x-pack/plugins/fleet/scripts/readme.md diff --git a/x-pack/plugins/ingest_manager/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/agent_collectors.ts rename to x-pack/plugins/fleet/server/collectors/agent_collectors.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts b/x-pack/plugins/fleet/server/collectors/config_collectors.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts rename to x-pack/plugins/fleet/server/collectors/config_collectors.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/helpers.ts b/x-pack/plugins/fleet/server/collectors/helpers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/helpers.ts rename to x-pack/plugins/fleet/server/collectors/helpers.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/package_collectors.ts b/x-pack/plugins/fleet/server/collectors/package_collectors.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/package_collectors.ts rename to x-pack/plugins/fleet/server/collectors/package_collectors.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/register.ts b/x-pack/plugins/fleet/server/collectors/register.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/register.ts rename to x-pack/plugins/fleet/server/collectors/register.ts diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/constants/index.ts rename to x-pack/plugins/fleet/server/constants/index.ts diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.test.ts b/x-pack/plugins/fleet/server/errors/handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/errors/handlers.test.ts rename to x-pack/plugins/fleet/server/errors/handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/errors/handlers.ts rename to x-pack/plugins/fleet/server/errors/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/errors/index.ts rename to x-pack/plugins/fleet/server/errors/index.ts diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/fleet/server/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/index.ts rename to x-pack/plugins/fleet/server/index.ts diff --git a/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts b/x-pack/plugins/fleet/server/integration_tests/router.test.ts similarity index 97% rename from x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts rename to x-pack/plugins/fleet/server/integration_tests/router.test.ts index 46b10ff381a606..c110ad23b61917 100644 --- a/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/router.test.ts @@ -21,7 +21,7 @@ function createXPackRoot(config: {} = {}) { scanDirs: [], paths: [ resolve(__dirname, '../../../../../x-pack/plugins/encrypted_saved_objects'), - resolve(__dirname, '../../../../../x-pack/plugins/ingest_manager'), + resolve(__dirname, '../../../../../x-pack/plugins/fleet'), resolve(__dirname, '../../../../../x-pack/plugins/licensing'), ], }, @@ -94,7 +94,7 @@ describe('ingestManager', () => { // For now, only the manager routes (/agent_policies & /package_policies) are added // EPM and ingest will be conditionally added when we enable these lines - // https://github.com/jfsiii/kibana/blob/f73b54ebb7e0f6fc00efd8a6800a01eb2d9fb772/x-pack/plugins/ingest_manager/server/plugin.ts#L84 + // https://github.com/jfsiii/kibana/blob/f73b54ebb7e0f6fc00efd8a6800a01eb2d9fb772/x-pack/plugins/fleet/server/plugin.ts#L84 // adding tests to confirm the Fleet & EPM routes are never added describe('manager and EPM; no Fleet', () => { diff --git a/x-pack/plugins/ingest_manager/server/mocks.ts b/x-pack/plugins/fleet/server/mocks.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/mocks.ts rename to x-pack/plugins/fleet/server/mocks.ts diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/plugin.ts rename to x-pack/plugins/fleet/server/plugin.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts rename to x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts rename to x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts rename to x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts rename to x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts rename to x-pack/plugins/fleet/server/routes/agent/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/index.ts rename to x-pack/plugins/fleet/server/routes/agent/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts rename to x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts rename to x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts rename to x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_policy/index.ts b/x-pack/plugins/fleet/server/routes/agent_policy/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent_policy/index.ts rename to x-pack/plugins/fleet/server/routes/agent_policy/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/app/index.ts rename to x-pack/plugins/fleet/server/routes/app/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts rename to x-pack/plugins/fleet/server/routes/data_streams/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/data_streams/index.ts b/x-pack/plugins/fleet/server/routes/data_streams/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/data_streams/index.ts rename to x-pack/plugins/fleet/server/routes/data_streams/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/handler.ts rename to x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts rename to x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts rename to x-pack/plugins/fleet/server/routes/epm/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts b/x-pack/plugins/fleet/server/routes/epm/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/epm/index.ts rename to x-pack/plugins/fleet/server/routes/epm/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/index.ts b/x-pack/plugins/fleet/server/routes/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/index.ts rename to x-pack/plugins/fleet/server/routes/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/install_script/index.ts b/x-pack/plugins/fleet/server/routes/install_script/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/install_script/index.ts rename to x-pack/plugins/fleet/server/routes/install_script/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts rename to x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts rename to x-pack/plugins/fleet/server/routes/limited_concurrency.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/output/handler.ts b/x-pack/plugins/fleet/server/routes/output/handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/output/handler.ts rename to x-pack/plugins/fleet/server/routes/output/handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/output/index.ts b/x-pack/plugins/fleet/server/routes/output/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/output/index.ts rename to x-pack/plugins/fleet/server/routes/output/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts rename to x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts rename to x-pack/plugins/fleet/server/routes/package_policy/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/index.ts b/x-pack/plugins/fleet/server/routes/package_policy/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/package_policy/index.ts rename to x-pack/plugins/fleet/server/routes/package_policy/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/settings/index.ts b/x-pack/plugins/fleet/server/routes/settings/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/settings/index.ts rename to x-pack/plugins/fleet/server/routes/settings/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts rename to x-pack/plugins/fleet/server/routes/setup/handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts rename to x-pack/plugins/fleet/server/routes/setup/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts b/x-pack/plugins/fleet/server/routes/setup/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/setup/index.ts rename to x-pack/plugins/fleet/server/routes/setup/index.ts diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/saved_objects/index.ts rename to x-pack/plugins/fleet/server/saved_objects/index.ts diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts rename to x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts rename to x-pack/plugins/fleet/server/services/agent_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agent_policy.ts rename to x-pack/plugins/fleet/server/services/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts rename to x-pack/plugins/fleet/server/services/agent_policy_update.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/fleet/server/services/agents/acks.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts rename to x-pack/plugins/fleet/server/services/agents/acks.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/fleet/server/services/agents/acks.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/acks.ts rename to x-pack/plugins/fleet/server/services/agents/acks.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts b/x-pack/plugins/fleet/server/services/agents/actions.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts rename to x-pack/plugins/fleet/server/services/agents/actions.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/actions.ts rename to x-pack/plugins/fleet/server/services/agents/actions.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts b/x-pack/plugins/fleet/server/services/agents/authenticate.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts rename to x-pack/plugins/fleet/server/services/agents/authenticate.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts b/x-pack/plugins/fleet/server/services/agents/authenticate.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts rename to x-pack/plugins/fleet/server/services/agents/authenticate.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts b/x-pack/plugins/fleet/server/services/agents/checkin/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts b/x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts b/x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_connected_agents.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state_connected_agents.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.test.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.test.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/crud.ts rename to x-pack/plugins/fleet/server/services/agents/crud.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts b/x-pack/plugins/fleet/server/services/agents/enroll.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts rename to x-pack/plugins/fleet/server/services/agents/enroll.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/fleet/server/services/agents/enroll.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/enroll.ts rename to x-pack/plugins/fleet/server/services/agents/enroll.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/events.ts b/x-pack/plugins/fleet/server/services/agents/events.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/events.ts rename to x-pack/plugins/fleet/server/services/agents/events.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/index.ts b/x-pack/plugins/fleet/server/services/agents/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/index.ts rename to x-pack/plugins/fleet/server/services/agents/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/reassign.ts rename to x-pack/plugins/fleet/server/services/agents/reassign.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts b/x-pack/plugins/fleet/server/services/agents/saved_objects.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts rename to x-pack/plugins/fleet/server/services/agents/saved_objects.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/setup.ts b/x-pack/plugins/fleet/server/services/agents/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/setup.ts rename to x-pack/plugins/fleet/server/services/agents/setup.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.test.ts b/x-pack/plugins/fleet/server/services/agents/status.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/status.test.ts rename to x-pack/plugins/fleet/server/services/agents/status.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/status.ts rename to x-pack/plugins/fleet/server/services/agents/status.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts rename to x-pack/plugins/fleet/server/services/agents/unenroll.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/update.ts b/x-pack/plugins/fleet/server/services/agents/update.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/update.ts rename to x-pack/plugins/fleet/server/services/agents/update.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts rename to x-pack/plugins/fleet/server/services/agents/upgrade.ts diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts rename to x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/index.ts b/x-pack/plugins/fleet/server/services/api_keys/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/api_keys/index.ts rename to x-pack/plugins/fleet/server/services/api_keys/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/security.ts b/x-pack/plugins/fleet/server/services/api_keys/security.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/api_keys/security.ts rename to x-pack/plugins/fleet/server/services/api_keys/security.ts diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/app_context.ts rename to x-pack/plugins/fleet/server/services/app_context.ts diff --git a/x-pack/plugins/ingest_manager/server/services/config.ts b/x-pack/plugins/fleet/server/services/config.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/config.ts rename to x-pack/plugins/fleet/server/services/config.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts rename to x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts rename to x-pack/plugins/fleet/server/services/epm/agent/agent.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts rename to x-pack/plugins/fleet/server/services/epm/archive/cache.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts rename to x-pack/plugins/fleet/server/services/epm/archive/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts rename to x-pack/plugins/fleet/server/services/epm/archive/validation.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/index.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/index.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/index.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/index.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/remove.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts similarity index 98% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 6a4d1ca0e1d0ab..25d412b685904e 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -219,7 +219,7 @@ function buildComponentTemplates(registryElasticsearch: RegistryElasticsearch | // temporary change until https://github.com/elastic/elasticsearch/issues/58956 is resolved // hopefully we'll be able to remove the entire properties section once that issue is resolved properties: { - // if the timestamp_field changes here: https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts#L309 + // if the timestamp_field changes here: https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts#L309 // we'll need to update this as well '@timestamp': { type: 'date', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/common.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/common.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/common.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/common.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/transform.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/transform.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/__snapshots__/field.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/__snapshots__/field.test.ts.snap rename to x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts rename to x-pack/plugins/fleet/server/services/epm/fields/field.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts rename to x-pack/plugins/fleet/server/services/epm/fields/field.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/tests/base.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/tests/base.yml rename to x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/tests/coredns.logs.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/coredns.logs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/tests/coredns.logs.yml rename to x-pack/plugins/fleet/server/services/epm/fields/tests/coredns.logs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/tests/system.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/system.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/tests/system.yml rename to x-pack/plugins/fleet/server/services/epm/fields/tests/system.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/test_data.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/test_data.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts rename to x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts rename to x-pack/plugins/fleet/server/services/epm/packages/assets.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/bulk_install_packages.ts rename to x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts rename to x-pack/plugins/fleet/server/services/epm/packages/get.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts rename to x-pack/plugins/fleet/server/services/epm/packages/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts rename to x-pack/plugins/fleet/server/services/epm/packages/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts rename to x-pack/plugins/fleet/server/services/epm/packages/remove.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts b/x-pack/plugins/fleet/server/services/epm/registry/extract.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts rename to x-pack/plugins/fleet/server/services/epm/registry/extract.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts rename to x-pack/plugins/fleet/server/services/epm/registry/index.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts rename to x-pack/plugins/fleet/server/services/epm/registry/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts rename to x-pack/plugins/fleet/server/services/epm/registry/proxy.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts rename to x-pack/plugins/fleet/server/services/epm/registry/proxy.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts rename to x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts rename to x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts rename to x-pack/plugins/fleet/server/services/epm/registry/requests.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts b/x-pack/plugins/fleet/server/services/epm/registry/streams.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts rename to x-pack/plugins/fleet/server/services/epm/registry/streams.ts diff --git a/x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts b/x-pack/plugins/fleet/server/services/es_index_pattern.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts rename to x-pack/plugins/fleet/server/services/es_index_pattern.ts diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/index.ts rename to x-pack/plugins/fleet/server/services/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/index.ts b/x-pack/plugins/fleet/server/services/install_script/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/index.ts rename to x-pack/plugins/fleet/server/services/install_script/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/install_templates/linux.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/install_templates/linux.ts rename to x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/install_templates/macos.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/install_templates/macos.ts rename to x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/install_templates/types.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/install_templates/types.ts rename to x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts diff --git a/x-pack/plugins/ingest_manager/server/services/license.ts b/x-pack/plugins/fleet/server/services/license.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/license.ts rename to x-pack/plugins/fleet/server/services/license.ts diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/output.ts rename to x-pack/plugins/fleet/server/services/output.ts diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/package_policy.test.ts rename to x-pack/plugins/fleet/server/services/package_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/package_policy.ts rename to x-pack/plugins/fleet/server/services/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/services/saved_object.test.ts b/x-pack/plugins/fleet/server/services/saved_object.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/saved_object.test.ts rename to x-pack/plugins/fleet/server/services/saved_object.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/saved_object.ts b/x-pack/plugins/fleet/server/services/saved_object.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/saved_object.ts rename to x-pack/plugins/fleet/server/services/saved_object.ts diff --git a/x-pack/plugins/ingest_manager/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/settings.ts rename to x-pack/plugins/fleet/server/services/settings.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup.test.ts rename to x-pack/plugins/fleet/server/services/setup.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup.ts rename to x-pack/plugins/fleet/server/services/setup.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts b/x-pack/plugins/fleet/server/services/setup_utils.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts rename to x-pack/plugins/fleet/server/services/setup_utils.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup_utils.ts b/x-pack/plugins/fleet/server/services/setup_utils.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup_utils.ts rename to x-pack/plugins/fleet/server/services/setup_utils.ts diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/index.tsx rename to x-pack/plugins/fleet/server/types/index.tsx diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent.ts b/x-pack/plugins/fleet/server/types/models/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/agent.ts rename to x-pack/plugins/fleet/server/types/models/agent.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/agent_policy.ts rename to x-pack/plugins/fleet/server/types/models/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/enrollment_api_key.ts b/x-pack/plugins/fleet/server/types/models/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/enrollment_api_key.ts rename to x-pack/plugins/fleet/server/types/models/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/index.ts b/x-pack/plugins/fleet/server/types/models/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/index.ts rename to x-pack/plugins/fleet/server/types/models/index.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/output.ts rename to x-pack/plugins/fleet/server/types/models/output.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/package_policy.ts rename to x-pack/plugins/fleet/server/types/models/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts rename to x-pack/plugins/fleet/server/types/rest_spec/agent.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/agent_policy.ts rename to x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts b/x-pack/plugins/fleet/server/types/rest_spec/common.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts rename to x-pack/plugins/fleet/server/types/rest_spec/common.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/fleet/server/types/rest_spec/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/enrollment_api_key.ts rename to x-pack/plugins/fleet/server/types/rest_spec/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts rename to x-pack/plugins/fleet/server/types/rest_spec/epm.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts b/x-pack/plugins/fleet/server/types/rest_spec/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts rename to x-pack/plugins/fleet/server/types/rest_spec/index.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/install_script.ts b/x-pack/plugins/fleet/server/types/rest_spec/install_script.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/install_script.ts rename to x-pack/plugins/fleet/server/types/rest_spec/install_script.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts b/x-pack/plugins/fleet/server/types/rest_spec/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts rename to x-pack/plugins/fleet/server/types/rest_spec/output.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/package_policy.ts rename to x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts rename to x-pack/plugins/fleet/server/types/rest_spec/settings.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/setup.ts b/x-pack/plugins/fleet/server/types/rest_spec/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/setup.ts rename to x-pack/plugins/fleet/server/types/rest_spec/setup.ts diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index c654288aaceb81..5094aa2763a012 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -10,7 +10,7 @@ import { ManagementAppMountParams } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { CoreSetup, CoreStart } from '../../../../../src/core/public'; -import { IngestManagerSetup } from '../../../ingest_manager/public'; +import { IngestManagerSetup } from '../../../fleet/public'; import { IndexMgmtMetricsType } from '../types'; import { UiMetricService, NotificationService, HttpService } from './services'; import { ExtensionsService } from '../services'; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index 52528d3c511454..c15af4f19827b9 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -9,7 +9,7 @@ import { CoreSetup } from 'src/core/public'; import { ManagementAppMountParams } from 'src/plugins/management/public/'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -import { IngestManagerSetup } from '../../../ingest_manager/public'; +import { IngestManagerSetup } from '../../../fleet/public'; import { PLUGIN } from '../../common/constants'; import { ExtensionsService } from '../services'; import { IndexMgmtMetricsType, StartDependencies } from '../types'; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index f860b89b0ba0c7..34d060d9354158 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -5,7 +5,7 @@ */ import { ExtensionsSetup } from './services'; -import { IngestManagerSetup } from '../../ingest_manager/public'; +import { IngestManagerSetup } from '../../fleet/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; import { SharePluginStart } from '../../../../src/plugins/share/public'; diff --git a/x-pack/plugins/security_solution/README.md b/x-pack/plugins/security_solution/README.md index 6680dbf1a149bf..d9aa4a6cfebbe9 100644 --- a/x-pack/plugins/security_solution/README.md +++ b/x-pack/plugins/security_solution/README.md @@ -97,7 +97,7 @@ PACKAGE_REGISTRY_URL_OVERRIDE= { diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts index 8c2dabae21bbde..551e0ecfdcb4f0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts @@ -6,7 +6,7 @@ import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { cloneDeep } from 'lodash'; -import { PackagePolicy } from '../../../../../ingest_manager/common'; +import { PackagePolicy } from '../../../../../fleet/common'; export const migratePackagePolicyToV7110: SavedObjectMigrationFn = ( packagePolicyDoc diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 79157018c315aa..1d64578a6a7f11 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -5,7 +5,7 @@ */ import { ApplicationStart } from 'kibana/public'; -import { NewPackagePolicy, PackagePolicy } from '../../../../ingest_manager/common'; +import { NewPackagePolicy, PackagePolicy } from '../../../../fleet/common'; import { ManifestSchema } from '../schema/manifest'; export * from './trusted_apps'; diff --git a/x-pack/plugins/security_solution/public/app/home/setup.tsx b/x-pack/plugins/security_solution/public/app/home/setup.tsx index 3f4b0c19e70357..c3567e34a04114 100644 --- a/x-pack/plugins/security_solution/public/app/home/setup.tsx +++ b/x-pack/plugins/security_solution/public/app/home/setup.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; -import { IngestManagerStart } from '../../../../ingest_manager/public'; +import { IngestManagerStart } from '../../../../fleet/public'; export const Setup: React.FunctionComponent<{ ingestManager: IngestManagerStart; diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts index 48f826d1c3a912..01b14bd4112825 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts @@ -11,7 +11,7 @@ import { appRoutesService, CheckPermissionsResponse, BulkInstallPackagesResponse, -} from '../../../../../ingest_manager/common'; +} from '../../../../../fleet/common'; import { StartServices } from '../../../types'; import { useIngestEnabledCheck } from './ingest_enabled'; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts index 5f8ab723df107a..ff3fe7517e64ab 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IngestManagerStart, - registerPackagePolicyComponent, -} from '../../../../../ingest_manager/public'; +import { IngestManagerStart, registerPackagePolicyComponent } from '../../../../../fleet/public'; import { dataPluginMock, Start as DataPublicStartMock, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index dce135dd213b33..37a61b4adf0200 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -11,7 +11,7 @@ import { } from '../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../common/types'; import { GetPolicyListResponse } from '../../policy/types'; -import { GetPackagesResponse } from '../../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../../fleet/common'; import { EndpointState } from '../types'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 80b2d2b0192f8e..a78bd5fde32165 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -26,7 +26,7 @@ import { sendGetAgentPolicyList, sendGetFleetAgentsWithEndpoint, } from '../../policy/store/policy_list/services/ingest'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../ingest_manager/common'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../fleet/common'; import { metadataCurrentIndexPattern } from '../../../../../common/endpoint/constants'; import { IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index cd8ebb4d0ec508..5b14b7d658965d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -25,7 +25,7 @@ import { GetAgentPoliciesResponseItem, GetPackagesResponse, GetAgentsResponse, -} from '../../../../../../ingest_manager/common/types/rest_spec'; +} from '../../../../../../fleet/common/types/rest_spec'; import { GetPolicyListResponse } from '../../policy/types'; const generator = new EndpointDocGenerator('seed'); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index e3e2dc7b55a5e9..ec22c522c3d0a9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -14,7 +14,7 @@ import { MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; -import { GetPackagesResponse } from '../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../fleet/common'; import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; export interface EndpointState { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx index c0763a21f09476..dd7475361b950b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx @@ -29,7 +29,7 @@ import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app import { getEndpointDetailsPath } from '../../../../common/routing'; import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; -import { AgentDetailsReassignPolicyAction } from '../../../../../../../ingest_manager/public'; +import { AgentDetailsReassignPolicyAction } from '../../../../../../../fleet/public'; import { EndpointPolicyLink } from '../components/endpoint_policy_link'; const HostIds = styled(EuiListGroupItem)` diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index c5d3c3c25313d6..670e8dc965ad98 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -51,7 +51,7 @@ import { CreatePackagePolicyRouteState, AgentPolicyDetailsDeployAgentAction, pagePathGetters, -} from '../../../../../../ingest_manager/public'; +} from '../../../../../../fleet/public'; import { SecurityPageName } from '../../../../app/types'; import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts index f729dfbd9a29a5..f5a219bce4a6bd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GetAgentStatusResponse } from '../../../../../../../ingest_manager/common/types/rest_spec'; +import { GetAgentStatusResponse } from '../../../../../../../fleet/common/types/rest_spec'; import { PolicyData, UIPolicyConfig } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; import { PolicyDetailsState } from '../../types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts index 562cd6807f68a9..cfd053948922ba 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts @@ -6,10 +6,7 @@ import { PolicyData } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; -import { - GetAgentStatusResponse, - GetPackagesResponse, -} from '../../../../../../../ingest_manager/common'; +import { GetAgentStatusResponse, GetPackagesResponse } from '../../../../../../../fleet/common'; interface ServerReturnedPolicyListData { type: 'serverReturnedPolicyListData'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts index 03e65bd6f43cbe..524c44406bd337 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts @@ -8,7 +8,7 @@ import { PolicyListState } from '../../types'; import { Store, applyMiddleware, createStore } from 'redux'; import { coreMock } from '../../../../../../../../../src/core/public/mocks'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../ingest_manager/common'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../fleet/common'; import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts index fddd242e092ade..e57eb0e32e516f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts @@ -18,7 +18,7 @@ import { DeletePackagePoliciesResponse, DeletePackagePoliciesRequest, GetAgentStatusResponse, -} from '../../../../../../../ingest_manager/common'; +} from '../../../../../../../fleet/common'; export const policyListMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts index 00e7b0ca51ecf1..43a12868368c55 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts @@ -11,7 +11,7 @@ import { sendGetEndpointSpecificPackagePolicies, } from './ingest'; import { httpServiceMock } from '../../../../../../../../../../src/core/public/mocks'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../../ingest_manager/common'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../../fleet/common'; import { policyListApiPathHandlers } from '../test_mock_utils'; describe('ingest service', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts index 204dfe437815ea..a241f8d1c4556f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts @@ -15,7 +15,7 @@ import { GetPackagesResponse, GetAgentPoliciesRequest, GetAgentPoliciesResponse, -} from '../../../../../../../../ingest_manager/common'; +} from '../../../../../../../../fleet/common'; import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; import { NewPolicyData } from '../../../../../../../common/endpoint/types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts index 1093aed0608d56..3c8b1f913c868e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts @@ -8,7 +8,7 @@ import { HttpStart } from 'kibana/public'; import { INGEST_API_EPM_PACKAGES, INGEST_API_PACKAGE_POLICIES } from './services/ingest'; import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; import { GetPolicyListResponse } from '../../types'; -import { GetPackagesResponse } from '../../../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../../../fleet/common'; const generator = new EndpointDocGenerator('policy-list'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index c7d426da055084..152caff3714b0a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -18,7 +18,7 @@ import { GetPackagePoliciesResponse, GetPackagesResponse, UpdatePackagePolicyResponse, -} from '../../../../../ingest_manager/common'; +} from '../../../../../fleet/common'; /** * Policy list store state diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx index 05e77c57ae52b2..0be5f119e5eff8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx @@ -23,7 +23,7 @@ import { CustomConfigurePackagePolicyContent, CustomConfigurePackagePolicyProps, pagePathGetters, -} from '../../../../../../../ingest_manager/public'; +} from '../../../../../../../fleet/public'; import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing'; import { MANAGEMENT_APP_ID } from '../../../../common/constants'; import { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 78226a858bfeb1..274032eea0c5d6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -46,7 +46,7 @@ import { SecurityPageName } from '../../../../app/types'; import { useFormatUrl } from '../../../../common/components/link_to'; import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { CreatePackagePolicyRouteState } from '../../../../../../ingest_manager/public'; +import { CreatePackagePolicyRouteState } from '../../../../../../fleet/public'; import { MANAGEMENT_APP_ID } from '../../../common/constants'; import { AdministrationListPage } from '../../../components/administration_list_page'; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index d72a8b92615872..5e8400c5742358 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -15,7 +15,7 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { TelemetryManagementSectionPluginSetup } from '../../../../src/plugins/telemetry_management_section/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; -import { IngestManagerStart } from '../../ingest_manager/public'; +import { IngestManagerStart } from '../../fleet/public'; import { PluginStart as ListsPluginStart } from '../../lists/public'; import { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts index 3796c3f62d0eee..9ad094086b6320 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts @@ -11,11 +11,11 @@ import { KbnClient, ToolingLog } from '@kbn/dev-utils'; import { AxiosResponse } from 'axios'; import { indexHostsAndAlerts } from '../../common/endpoint/index_data'; import { ANCESTRY_LIMIT, EndpointDocGenerator } from '../../common/endpoint/generate_data'; -import { AGENTS_SETUP_API_ROUTES, SETUP_API_ROUTE } from '../../../ingest_manager/common/constants'; +import { AGENTS_SETUP_API_ROUTES, SETUP_API_ROUTE } from '../../../fleet/common/constants'; import { CreateFleetSetupResponse, PostIngestSetupResponse, -} from '../../../ingest_manager/common/types/rest_spec'; +} from '../../../fleet/common/types/rest_spec'; import { KbnClientWithApiKeySupport } from './kbn_client_with_api_key_support'; main(); diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 2cc8245e521bf1..c7f49f479583ec 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -10,11 +10,7 @@ import { SavedObjectsClientContract, } from 'src/core/server'; import { SecurityPluginSetup } from '../../../security/server'; -import { - AgentService, - IngestManagerStartContract, - PackageService, -} from '../../../ingest_manager/server'; +import { AgentService, IngestManagerStartContract, PackageService } from '../../../fleet/server'; import { PluginStartContract as AlertsPluginStartContract } from '../../../alerts/server'; import { getPackagePolicyCreateCallback } from './ingest_integration'; import { ManifestManager } from './services/artifacts'; @@ -24,7 +20,7 @@ import { metadataQueryStrategyV1, metadataQueryStrategyV2, } from './routes/metadata/support/query_strategies'; -import { ElasticsearchAssetType } from '../../../ingest_manager/common/types/models'; +import { ElasticsearchAssetType } from '../../../fleet/common/types/models'; import { metadataTransformPrefix } from '../../common/endpoint/constants'; import { AppClientFactory } from '../client'; import { ConfigType } from '../config'; diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts index 1db3e9984284d6..321d67a441f6dc 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts @@ -5,7 +5,7 @@ */ import { httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; -import { createNewPackagePolicyMock } from '../../../ingest_manager/common/mocks'; +import { createNewPackagePolicyMock } from '../../../fleet/common/mocks'; import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config'; import { getManifestManagerMock, diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts index 279603cd621c8e..3c94bdc63b0fb8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts @@ -6,9 +6,9 @@ import { PluginStartContract as AlertsStartContract } from '../../../alerts/server'; import { SecurityPluginSetup } from '../../../security/server'; -import { ExternalCallback } from '../../../ingest_manager/server'; +import { ExternalCallback } from '../../../fleet/server'; import { KibanaRequest, Logger, RequestHandlerContext } from '../../../../../src/core/server'; -import { NewPackagePolicy } from '../../../ingest_manager/common/types/models'; +import { NewPackagePolicy } from '../../../fleet/common/types/models'; import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config'; import { NewPolicyData } from '../../common/endpoint/types'; import { ManifestManager } from './services/artifacts'; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts index cdfbb551940e1b..44b1d5fefe4e19 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PackagePolicy } from '../../../../../ingest_manager/common'; -import { createPackagePolicyMock } from '../../../../../ingest_manager/common/mocks'; +import { PackagePolicy } from '../../../../../fleet/common'; +import { createPackagePolicyMock } from '../../../../../fleet/common/mocks'; import { InternalArtifactCompleteSchema } from '../../schemas/artifacts'; import { getInternalArtifactMock, diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 98b971a00710d2..588404fd516d06 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -14,8 +14,8 @@ import { IngestManagerStartContract, ExternalCallback, PackageService, -} from '../../../ingest_manager/server'; -import { createPackagePolicyServiceMock } from '../../../ingest_manager/server/mocks'; +} from '../../../fleet/server'; +import { createPackagePolicyServiceMock } from '../../../fleet/server/mocks'; import { AppClientFactory } from '../client'; import { createMockConfig } from '../lib/detection_engine/routes/__mocks__'; import { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts index d825841f1e2576..401f7e97470be1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts @@ -12,7 +12,7 @@ import { } from 'src/core/server'; import LRU from 'lru-cache'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { authenticateAgentWithAccessToken } from '../../../../../ingest_manager/server/services/agents/authenticate'; +import { authenticateAgentWithAccessToken } from '../../../../../fleet/server/services/agents/authenticate'; import { LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG } from '../../../../common/endpoint/constants'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; import { ArtifactConstants } from '../../lib/artifacts'; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 194f0a1c2e7c52..f2011e99565c80 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -15,7 +15,7 @@ import { MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders'; -import { Agent, AgentStatus } from '../../../../../ingest_manager/common/types/models'; +import { Agent, AgentStatus } from '../../../../../fleet/common/types/models'; import { EndpointAppContext, HostListQueryResult } from '../../types'; import { GetMetadataListRequestSchema, GetMetadataRequestSchema } from './index'; import { findAllUnenrolledAgentIds } from './support/unenroll'; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 227a3e6fd96574..46a4363936b3de 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -39,9 +39,9 @@ import { Agent, ElasticsearchAssetType, EsAssetReference, -} from '../../../../../ingest_manager/common/types/models'; +} from '../../../../../fleet/common/types/models'; import { createV1SearchResponse, createV2SearchResponse } from './support/test_support'; -import { PackageService } from '../../../../../ingest_manager/server/services'; +import { PackageService } from '../../../../../fleet/server/services'; import { metadataTransformPrefix } from '../../../../common/endpoint/constants'; describe('test endpoint route', () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts index 568917c804733f..26f216f0474c27 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts @@ -35,9 +35,9 @@ import { import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { Agent, EsAssetReference } from '../../../../../ingest_manager/common/types/models'; +import { Agent, EsAssetReference } from '../../../../../fleet/common/types/models'; import { createV1SearchResponse } from './support/test_support'; -import { PackageService } from '../../../../../ingest_manager/server/services'; +import { PackageService } from '../../../../../fleet/server/services'; describe('test endpoint route v1', () => { let routerMock: jest.Mocked; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts index a4b6b0750ec10d..ed3c48ed6c6770 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts @@ -7,10 +7,10 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { findAgentIDsByStatus } from './agent_status'; import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks'; -import { AgentService } from '../../../../../../ingest_manager/server/services'; +import { AgentService } from '../../../../../../fleet/server/services'; import { createMockAgentService } from '../../../mocks'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; -import { AgentStatusKueryHelper } from '../../../../../../ingest_manager/common/services'; +import { Agent } from '../../../../../../fleet/common/types/models'; +import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services'; describe('test filtering endpoint hosts by agent status', () => { let mockSavedObjectClient: jest.Mocked; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts index 86f6d1a9a65e22..395b05c0887e96 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts @@ -5,9 +5,9 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentService } from '../../../../../../ingest_manager/server'; -import { AgentStatusKueryHelper } from '../../../../../../ingest_manager/common/services'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; +import { AgentService } from '../../../../../../fleet/server'; +import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services'; +import { Agent } from '../../../../../../fleet/common/types/models'; import { HostStatus } from '../../../../../common/endpoint/types'; const STATUS_QUERY_MAP = new Map([ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts index 30c8f14287cae1..cd273f785033c8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts @@ -7,9 +7,9 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { findAllUnenrolledAgentIds } from './unenroll'; import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks'; -import { AgentService } from '../../../../../../ingest_manager/server/services'; +import { AgentService } from '../../../../../../fleet/server/services'; import { createMockAgentService } from '../../../mocks'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; +import { Agent } from '../../../../../../fleet/common/types/models'; describe('test find all unenrolled Agent id', () => { let mockSavedObjectClient: jest.Mocked; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts index ca2ced177ab331..1abea86c1a4955 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts @@ -5,8 +5,8 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentService } from '../../../../../../ingest_manager/server'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; +import { AgentService } from '../../../../../../fleet/server'; +import { Agent } from '../../../../../../fleet/common/types/models'; export async function findAllUnenrolledAgentIds( agentService: AgentService, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index 27dd38047e7c36..353bfb6b0f16e4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -6,8 +6,8 @@ import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks'; import { Logger } from 'src/core/server'; -import { PackagePolicyServiceInterface } from '../../../../../../ingest_manager/server'; -import { createPackagePolicyServiceMock } from '../../../../../../ingest_manager/server/mocks'; +import { PackagePolicyServiceInterface } from '../../../../../../fleet/server'; +import { createPackagePolicyServiceMock } from '../../../../../../fleet/server/mocks'; import { ExceptionListClient } from '../../../../../../lists/server'; import { listMock } from '../../../../../../lists/server/mocks'; import LRU from 'lru-cache'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 40b408166b17f4..f5a27b7602e8b3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -6,7 +6,7 @@ import { inflateSync } from 'zlib'; import { savedObjectsClientMock } from 'src/core/server/mocks'; -import { createPackagePolicyServiceMock } from '../../../../../../ingest_manager/server/mocks'; +import { createPackagePolicyServiceMock } from '../../../../../../fleet/server/mocks'; import { ArtifactConstants, ManifestConstants, isCompleteArtifact } from '../../../lib/artifacts'; import { getManifestManagerMock, ManifestManagerMockType } from './manifest_manager.mock'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index f9836c7ecdc30c..9f45f39a392f67 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -6,7 +6,7 @@ import semver from 'semver'; import { Logger, SavedObjectsClientContract } from 'src/core/server'; import LRU from 'lru-cache'; -import { PackagePolicyServiceInterface } from '../../../../../../ingest_manager/server'; +import { PackagePolicyServiceInterface } from '../../../../../../fleet/server'; import { ExceptionListClient } from '../../../../../../lists/server'; import { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common'; import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/manifest'; diff --git a/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts index 6abff93d6cd5c2..53e3da62f4e35d 100644 --- a/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts @@ -34,8 +34,8 @@ import { createMockEndpointAppContextServiceStartContract, createMockPackageService, } from '../../endpoint/mocks'; -import { PackageService } from '../../../../ingest_manager/server/services'; -import { ElasticsearchAssetType } from '../../../../ingest_manager/common/types/models'; +import { PackageService } from '../../../../fleet/server/services'; +import { ElasticsearchAssetType } from '../../../../fleet/common/types/models'; jest.mock('./query.hosts.dsl', () => { return { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 43f87a0c69ab39..d8faa2436bf552 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -34,7 +34,7 @@ import { ListPluginSetup } from '../../lists/server'; import { EncryptedSavedObjectsPluginSetup as EncryptedSavedObjectsSetup } from '../../encrypted_saved_objects/server'; import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server'; import { LicensingPluginSetup } from '../../licensing/server'; -import { IngestManagerStartContract, ExternalCallback } from '../../ingest_manager/server'; +import { IngestManagerStartContract, ExternalCallback } from '../../fleet/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { initServer } from './init_server'; import { compose } from './lib/compose/kibana'; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts index d5e1d21b7772be..b8858100171f93 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsFindResponse } from 'src/core/server'; -import { AgentEventSOAttributes } from './../../../../ingest_manager/common/types/models/agent'; +import { AgentEventSOAttributes } from './../../../../fleet/common/types/models/agent'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, -} from '../../../../ingest_manager/common/constants/agent'; -import { Agent } from '../../../../ingest_manager/common'; +} from '../../../../fleet/common/constants/agent'; +import { Agent } from '../../../../fleet/common'; import { FLEET_ENDPOINT_PACKAGE_CONSTANT } from './fleet_saved_objects'; const testAgentId = 'testAgentId'; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts index e2f7a3be6d80a5..6d4b97564c90ef 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts @@ -12,8 +12,8 @@ import { MockOSVersion, } from './endpoint.mocks'; import { ISavedObjectsRepository, SavedObjectsFindResponse } from 'src/core/server'; -import { AgentEventSOAttributes } from '../../../../ingest_manager/common/types/models/agent'; -import { Agent } from '../../../../ingest_manager/common'; +import { AgentEventSOAttributes } from '../../../../fleet/common/types/models/agent'; +import { Agent } from '../../../../fleet/common'; import * as endpointTelemetry from './index'; import * as fleetSavedObjects from './fleet_saved_objects'; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts index 405339aaf25e22..7ffb53dcf2a3f0 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts @@ -5,12 +5,12 @@ */ import { ISavedObjectsRepository } from 'src/core/server'; -import { AgentEventSOAttributes } from './../../../../ingest_manager/common/types/models/agent'; +import { AgentEventSOAttributes } from './../../../../fleet/common/types/models/agent'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, -} from './../../../../ingest_manager/common/constants/agent'; -import { Agent, defaultPackages as FleetDefaultPackages } from '../../../../ingest_manager/common'; +} from './../../../../fleet/common/constants/agent'; +import { Agent, defaultPackages as FleetDefaultPackages } from '../../../../fleet/common'; export const FLEET_ENDPOINT_PACKAGE_CONSTANT = FleetDefaultPackages.Endpoint; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts index f1ce9d3ad6ff31..f1642c52ef405f 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts @@ -6,8 +6,8 @@ import { cloneDeep } from 'lodash'; import { ISavedObjectsRepository } from 'src/core/server'; import { SavedObject } from './../../../../../../src/core/types/saved_objects'; -import { Agent, NewAgentEvent } from './../../../../ingest_manager/common/types/models/agent'; -import { AgentMetadata } from '../../../../ingest_manager/common/types/models/agent'; +import { Agent, NewAgentEvent } from './../../../../fleet/common/types/models/agent'; +import { AgentMetadata } from '../../../../fleet/common/types/models/agent'; import { getFleetSavedObjectsMetadata, getLatestFleetEndpointEvent } from './fleet_saved_objects'; export interface AgentOSMetadataTelemetry { diff --git a/x-pack/test/common/services/ingest_manager.ts b/x-pack/test/common/services/ingest_manager.ts index 2fcfaa014b2e12..42c1d20437c7b1 100644 --- a/x-pack/test/common/services/ingest_manager.ts +++ b/x-pack/test/common/services/ingest_manager.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { FtrProviderContext } from '../ftr_provider_context'; -import { fleetSetupRouteService } from '../../../plugins/ingest_manager/common'; +import { fleetSetupRouteService } from '../../../plugins/fleet/common'; export function IngestManagerProvider({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts index 7c07fd47a66af4..3f32d445e1fe7c 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts @@ -11,7 +11,7 @@ import { BulkInstallPackageInfo, BulkInstallPackagesResponse, IBulkInstallPackageHTTPError, -} from '../../../../plugins/ingest_manager/common'; +} from '../../../../plugins/fleet/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts index 8e8e4f010bcb55..2ae4273bfa7e87 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import { AssetReference } from '../../../../plugins/ingest_manager/common'; +import { AssetReference } from '../../../../plugins/fleet/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts index af64d83a8a1397..429d70836a6c1b 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts @@ -10,7 +10,7 @@ import { skipIfNoDockerRegistry } from '../../helpers'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL, -} from '../../../../plugins/ingest_manager/common'; +} from '../../../../plugins/fleet/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts index 2e7ab199a7fbc7..edd6cd4e107a5b 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL, -} from '../../../../plugins/ingest_manager/common'; +} from '../../../../plugins/fleet/common'; import { skipIfNoDockerRegistry } from '../../helpers'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts index 15cff79475f082..3bab7411bed88f 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; -import { GetInfoResponse, Installed } from '../../../../plugins/ingest_manager/common'; +import { GetInfoResponse, Installed } from '../../../../plugins/fleet/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts index c92dac3334de30..1fc3eac026b655 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { getTemplate } from '../../../../plugins/ingest_manager/server/services/epm/elasticsearch/template/template'; +import { getTemplate } from '../../../../plugins/fleet/server/services/epm/elasticsearch/template/template'; export default function ({ getService }: FtrProviderContext) { const indexPattern = 'foo'; diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts index 12d7780ad50d40..cbb07b91caf34b 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts @@ -9,7 +9,7 @@ import semver from 'semver'; import { FtrProviderContext } from '../../../../api_integration/ftr_provider_context'; import { setupIngest } from './services'; import { skipIfNoDockerRegistry } from '../../../helpers'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../../../plugins/ingest_manager/common'; +import { AGENT_SAVED_OBJECT_TYPE } from '../../../../../plugins/fleet/common'; const makeSnapshotVersion = (version: string) => { return version.endsWith('-SNAPSHOT') ? version : `${version}-SNAPSHOT`; 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 3e3aeee3054338..3103d461669f1e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../../plugins/ingest_manager/server'; +import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../../plugins/fleet/server'; import { FtrProviderContext } from '../../ftr_provider_context'; import { isRegistryEnabled, 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 7fc51f5c8e39cb..1b1d0bf96a1874 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts @@ -15,7 +15,7 @@ import { GetPackagePoliciesResponse, GetFullAgentPolicyResponse, GetPackagesResponse, -} from '../../../plugins/ingest_manager/common'; +} from '../../../plugins/fleet/common'; import { factory as policyConfigFactory } from '../../../plugins/security_solution/common/endpoint/models/policy_config'; import { Immutable } from '../../../plugins/security_solution/common/endpoint/types'; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index 3d344c1b3b51b6..cd0a861d9a2f0b 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -5,7 +5,7 @@ */ import { FtrProviderContext } from '../ftr_provider_context'; import { isRegistryEnabled, getRegistryUrlFromTestEnv } from '../registry'; -import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../plugins/ingest_manager/server'; +import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../plugins/fleet/server'; export default function endpointAPIIntegrationTests(providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; From 89547bc0166e375e3249edffd94dbb2797085f52 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 9 Nov 2020 10:07:59 -0600 Subject: [PATCH 72/81] [deb/rpm] Set pid.file to /run/kibana/kibana.pid (#82209) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../service_templates/systemd/etc/systemd/system/kibana.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service index 05724db8799f32..7a1508d91b213b 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service @@ -15,7 +15,7 @@ Environment=KBN_PATH_CONF=/etc/kibana EnvironmentFile=-/etc/default/kibana EnvironmentFile=-/etc/sysconfig/kibana -ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" +ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" --pid.file="/run/kibana/kibana.pid" Restart=on-failure RestartSec=3 From bc9dd3ade0b0361cbab79b76391235828588fcab Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 9 Nov 2020 11:29:31 -0500 Subject: [PATCH 73/81] Allow the repository to search across all namespaces (#82863) --- .../lib/search_dsl/query_params.test.ts | 21 +++++--- .../service/lib/search_dsl/query_params.ts | 51 +++++++++---------- .../apis/saved_objects/find.js | 25 ++++++++- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index a8c5df8d646305..c35ec809fcf8d3 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -21,8 +21,8 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; +import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; import { SavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; -import { ALL_NAMESPACES_STRING } from '../utils'; import { getQueryParams, getClauseForReference } from './query_params'; const registerTypes = (registry: SavedObjectTypeRegistry) => { @@ -101,20 +101,25 @@ describe('#getQueryParams', () => { const createTypeClause = (type: string, namespaces?: string[]) => { if (registry.isMultiNamespace(type)) { - const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; + const array = [...(namespaces ?? [DEFAULT_NAMESPACE_STRING]), ALL_NAMESPACES_STRING]; + + const namespacesClause = { terms: { namespaces: array } }; return { bool: { - must: expect.arrayContaining([{ terms: { namespaces: array } }]), + must: namespaces?.includes(ALL_NAMESPACES_STRING) + ? expect.not.arrayContaining([namespacesClause]) + : expect.arrayContaining([namespacesClause]), must_not: [{ exists: { field: 'namespace' } }], }, }; } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const nonDefaultNamespaces = namespaces?.filter((n) => n !== DEFAULT_NAMESPACE_STRING) ?? []; + const searchingAcrossAllNamespaces = namespaces?.includes(ALL_NAMESPACES_STRING) ?? false; const should: any = []; - if (nonDefaultNamespaces.length > 0) { + if (nonDefaultNamespaces.length > 0 && !searchingAcrossAllNamespaces) { should.push({ terms: { namespace: nonDefaultNamespaces } }); } - if (namespaces?.includes('default')) { + if (namespaces?.includes(DEFAULT_NAMESPACE_STRING)) { should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); } return { @@ -352,7 +357,7 @@ describe('#getQueryParams', () => { expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces))); }; - it('normalizes and deduplicates provided namespaces', () => { + it('deduplicates provided namespaces', () => { const result = getQueryParams({ registry, search: '*', @@ -361,7 +366,7 @@ describe('#getQueryParams', () => { expectResult( result, - ...ALL_TYPES.map((x) => createTypeClause(x, ['foo', 'default', 'bar'])) + ...ALL_TYPES.map((x) => createTypeClause(x, ['foo', '*', 'bar', 'default'])) ); }); diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index f73777c4f454fc..2ecba42e408e77 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -75,34 +75,41 @@ function getClauseForType( if (namespaces.length === 0) { throw new Error('cannot specify empty namespaces array'); } + const searchAcrossAllNamespaces = namespaces.includes(ALL_NAMESPACES_STRING); + if (registry.isMultiNamespace(type)) { + const namespacesFilterClause = searchAcrossAllNamespaces + ? {} + : { terms: { namespaces: [...namespaces, ALL_NAMESPACES_STRING] } }; + return { bool: { - must: [ - { term: { type } }, - { terms: { namespaces: [...namespaces, ALL_NAMESPACES_STRING] } }, - ], + must: [{ term: { type } }, namespacesFilterClause], must_not: [{ exists: { field: 'namespace' } }], }, }; } else if (registry.isSingleNamespace(type)) { const should: Array> = []; const eligibleNamespaces = namespaces.filter((x) => x !== DEFAULT_NAMESPACE_STRING); - if (eligibleNamespaces.length > 0) { + if (eligibleNamespaces.length > 0 && !searchAcrossAllNamespaces) { should.push({ terms: { namespace: eligibleNamespaces } }); } if (namespaces.includes(DEFAULT_NAMESPACE_STRING)) { should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); } - if (should.length === 0) { - // This is indicitive of a bug, and not user error. - throw new Error('unhandled search condition: expected at least 1 `should` clause.'); - } + + const shouldClauseProps = + should.length > 0 + ? { + should, + minimum_should_match: 1, + } + : {}; + return { bool: { must: [{ term: { type } }], - should, - minimum_should_match: 1, + ...shouldClauseProps, must_not: [{ exists: { field: 'namespaces' } }], }, }; @@ -181,21 +188,9 @@ export function getClauseForReference(reference: HasReferenceQueryParams) { }; } -// A de-duplicated set of namespaces makes for a more efficient query. -// -// Additionally, we treat the `*` namespace as the `default` namespace. -// In the Default Distribution, the `*` is automatically expanded to include all available namespaces. -// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` -// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, -// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place -// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. -// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 -const normalizeNamespaces = (namespacesToNormalize?: string[]) => - namespacesToNormalize - ? Array.from( - new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) - ) - : undefined; +// A de-duplicated set of namespaces makes for a more effecient query. +const uniqNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize ? Array.from(new Set(namespacesToNormalize)) : undefined; /** * Get the "query" related keys for the search body @@ -229,10 +224,10 @@ export function getQueryParams({ { bool: { should: types.map((shouldType) => { - const normalizedNamespaces = normalizeNamespaces( + const deduplicatedNamespaces = uniqNamespaces( typeToNamespacesMap ? typeToNamespacesMap.get(shouldType) : namespaces ); - return getClauseForType(registry, normalizedNamespaces, shouldType); + return getClauseForType(registry, deduplicatedNamespaces, shouldType); }), minimum_should_match: 1, }, diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index e5da46644672bb..8e8730b1e574a5 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -160,7 +160,7 @@ export default function ({ getService }) { }); describe('wildcard namespace', () => { - it('should return 200 with individual responses from the default namespace', async () => + it('should return 200 with individual responses from the all namespaces', async () => await supertest .get('/api/saved_objects/_find?type=visualization&fields=title&namespaces=*') .expect(200) @@ -168,7 +168,7 @@ export default function ({ getService }) { expect(resp.body).to.eql({ page: 1, per_page: 20, - total: 1, + total: 2, saved_objects: [ { type: 'visualization', @@ -189,6 +189,27 @@ export default function ({ getService }) { ], updated_at: '2017-09-21T18:51:23.794Z', }, + { + attributes: { + title: 'Count of requests', + }, + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + migrationVersion: { + visualization: '7.10.0', + }, + namespaces: ['foo-ns'], + references: [ + { + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + }, + ], + score: 0, + type: 'visualization', + updated_at: '2017-09-21T18:51:23.794Z', + version: 'WzYsMV0=', + }, ], }); expect(resp.body.saved_objects[0].migrationVersion).to.be.ok(); From 55cf3bd0a646b504db76b2050413b0101671b15c Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 9 Nov 2020 11:33:57 -0500 Subject: [PATCH 74/81] Update grunt and related packages (#79327) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 3 +- yarn.lock | 151 ++++++++++++++++++++++++++------------------------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index 3500ed78ad7d13..353d3dc85356c7 100644 --- a/package.json +++ b/package.json @@ -663,10 +663,9 @@ "graphql-codegen-typescript-common": "^0.18.2", "graphql-codegen-typescript-resolvers": "^0.18.2", "graphql-codegen-typescript-server": "^0.18.2", - "grunt": "1.0.4", + "grunt": "1.3.0", "grunt-available-tasks": "^0.6.3", "grunt-babel": "^8.0.0", - "grunt-cli": "^1.2.0", "grunt-contrib-clean": "^1.1.0", "grunt-contrib-copy": "^1.0.0", "grunt-contrib-watch": "^1.1.0", diff --git a/yarn.lock b/yarn.lock index edbc186c22243e..b59c134968c18f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9094,7 +9094,7 @@ chai@^4.1.2: pathval "^1.1.0" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2, chalk@~2.4.1: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9122,7 +9122,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@~4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -9690,11 +9690,6 @@ coffeescript@1.12.7: resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== -coffeescript@~1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.10.0.tgz#e7aa8301917ef621b35d8a39f348dcdd1db7e33e" - integrity sha1-56qDAZF+9iGzXYo580jc3R234z4= - collapse-white-space@^1.0.0, collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -11103,7 +11098,7 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -dateformat@^1.0.11, dateformat@~1.0.12: +dateformat@^1.0.11: version "1.0.12" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= @@ -11111,7 +11106,7 @@ dateformat@^1.0.11, dateformat@~1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -dateformat@^3.0.2: +dateformat@^3.0.2, dateformat@~3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== @@ -13151,7 +13146,7 @@ exit-hook@^2.2.0: resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.0.tgz#f5502f92179018e867f2d8ee4428392da7f3894e" integrity sha512-YFH+2oGdldRH5GqGpnaiKbBxWHMmuXHmKTMtUC58kWSOrnTf95rKITVSFTTtas14DWvWpih429+ffAvFetPwNA== -exit@^0.1.2, exit@~0.1.1: +exit@^0.1.2, exit@~0.1.1, exit@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= @@ -14586,7 +14581,7 @@ glob@^6.0.1, glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -14609,18 +14604,6 @@ glob@~5.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~7.0.0: - version "7.0.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" - integrity sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -15165,15 +15148,16 @@ grunt-babel@^8.0.0: resolved "https://registry.yarnpkg.com/grunt-babel/-/grunt-babel-8.0.0.tgz#92ef63aafadf938c488dc2f926ac9846e0c93d1b" integrity sha512-WuiZFvGzcyzlEoPIcY1snI234ydDWeWWV5bpnB7PZsOLHcDsxWKnrR1rMWEUsbdVPPjvIirwFNsuo4CbJmsdFQ== -grunt-cli@^1.2.0, grunt-cli@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8" - integrity sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg= +grunt-cli@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.3.2.tgz#60f12d12c1b5aae94ae3469c6b5fe24e960014e8" + integrity sha512-8OHDiZZkcptxVXtMfDxJvmN7MVJNE8L/yIcPb4HB7TlyFD1kDvjHrb62uhySsU14wJx9ORMnTuhRMQ40lH/orQ== dependencies: - findup-sync "~0.3.0" grunt-known-options "~1.1.0" - nopt "~3.0.6" - resolve "~1.1.0" + interpret "~1.1.0" + liftoff "~2.5.0" + nopt "~4.0.1" + v8flags "~3.1.1" grunt-contrib-clean@^1.1.0: version "1.1.0" @@ -15206,35 +15190,35 @@ grunt-known-options@~1.1.0: resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.0.tgz#a4274eeb32fa765da5a7a3b1712617ce3b144149" integrity sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk= -grunt-legacy-log-utils@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz#d2f442c7c0150065d9004b08fd7410d37519194e" - integrity sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA== +grunt-legacy-log-utils@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" + integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== dependencies: - chalk "~2.4.1" - lodash "~4.17.10" + chalk "~4.1.0" + lodash "~4.17.19" -grunt-legacy-log@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz#c8cd2c6c81a4465b9bbf2d874d963fef7a59ffb9" - integrity sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw== +grunt-legacy-log@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" + integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== dependencies: colors "~1.1.2" - grunt-legacy-log-utils "~2.0.0" + grunt-legacy-log-utils "~2.1.0" hooker "~0.2.3" - lodash "~4.17.5" + lodash "~4.17.19" -grunt-legacy-util@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz#e10624e7c86034e5b870c8a8616743f0a0845e42" - integrity sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A== +grunt-legacy-util@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.0.tgz#34d20f2a26c6adebfe9a9bdc8823f7016b0369c3" + integrity sha512-ZEmYFB44bblwPE2oz3q3ygfF6hseQja9tx8I3UZIwbUik32FMWewA+d1qSFicMFB+8dNXDkh35HcDCWlpRsGlA== dependencies: async "~1.5.2" exit "~0.1.1" getobject "~0.1.0" hooker "~0.2.3" - lodash "~4.17.10" - underscore.string "~3.3.4" + lodash "~4.17.20" + underscore.string "~3.3.5" which "~1.3.0" grunt-peg@^2.0.1: @@ -15251,28 +15235,26 @@ grunt-run@0.8.1: dependencies: strip-ansi "^3.0.0" -grunt@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.0.4.tgz#c799883945a53a3d07622e0737c8f70bfe19eb38" - integrity sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ== +grunt@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.3.0.tgz#55db6ccd80c6fb53722e496f680620a2e681f809" + integrity sha512-6ILlMXv11/4cxuhSMfSU+SfvbxrPuqZrAtLN64+tZpQ3DAKfSQPQHRbTjSbdtxfyQhGZPtN0bDZJ/LdCM5WXXA== dependencies: - coffeescript "~1.10.0" - dateformat "~1.0.12" + dateformat "~3.0.3" eventemitter2 "~0.4.13" - exit "~0.1.1" + exit "~0.1.2" findup-sync "~0.3.0" - glob "~7.0.0" - grunt-cli "~1.2.0" + glob "~7.1.6" + grunt-cli "~1.3.2" grunt-known-options "~1.1.0" - grunt-legacy-log "~2.0.0" - grunt-legacy-util "~1.1.1" + grunt-legacy-log "~3.0.0" + grunt-legacy-util "~2.0.0" iconv-lite "~0.4.13" - js-yaml "~3.13.0" - minimatch "~3.0.2" - mkdirp "~0.5.1" + js-yaml "~3.14.0" + minimatch "~3.0.4" + mkdirp "~1.0.4" nopt "~3.0.6" - path-is-absolute "~1.0.0" - rimraf "~2.6.2" + rimraf "~3.0.2" gud@^1.0.0: version "1.0.0" @@ -16431,6 +16413,11 @@ interpret@^2.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= + intl-format-cache@^2.0.5, intl-format-cache@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.1.0.tgz#04a369fecbfad6da6005bae1f14333332dcf9316" @@ -18070,7 +18057,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.0, js-yaml@~3.13.1: +js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -18078,7 +18065,7 @@ js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1 argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.14.0: +js-yaml@^3.14.0, js-yaml@~3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -18715,6 +18702,20 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" +liftoff@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" + integrity sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew= + dependencies: + extend "^3.0.0" + findup-sync "^2.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + linebreak@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.0.2.tgz#4b5781733e9a9eb2849dba2f963e47c887f8aa06" @@ -19189,7 +19190,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.19, lodash@~4.17.20: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -19937,7 +19938,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -20072,7 +20073,7 @@ mkdirp@^0.3.5: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= -mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -21879,7 +21880,7 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1, path-is-absolute@~1.0.0: +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -24610,7 +24611,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@~1.1.0, resolve@~1.1.7: +resolve@1.1.7, resolve@~1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= @@ -24715,7 +24716,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: +rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -24736,7 +24737,7 @@ rimraf@^2.7.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2, rimraf@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -27696,7 +27697,7 @@ undefsafe@^2.0.2: dependencies: debug "^2.2.0" -underscore.string@~3.3.4: +underscore.string@~3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg== @@ -28384,7 +28385,7 @@ v8-to-istanbul@^6.0.1: convert-source-map "^1.6.0" source-map "^0.7.3" -v8flags@^3.0.1: +v8flags@^3.0.1, v8flags@~3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== From 45ddd69ca220485ed481ed691e59ff1ea7edbeef Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 9 Nov 2020 17:34:20 +0100 Subject: [PATCH 75/81] Prevent Kerberos and PKI providers from initiating a new session for unauthenticated XHR/API requests. (#82817) * Prevent Kerberos and PKI providers from initiating a new session for unauthenticated XHR requests. * Review#1: fix comment. --- .github/CODEOWNERS | 3 - .../authentication/providers/kerberos.test.ts | 63 +++++--- .../authentication/providers/kerberos.ts | 37 ++--- .../authentication/providers/pki.test.ts | 138 ++++++++++++------ .../server/authentication/providers/pki.ts | 20 +-- x-pack/scripts/functional_tests.js | 10 +- .../kerberos_api_integration/apis/index.ts | 14 -- .../ftr_provider_context.d.ts | 11 -- .../test/kerberos_api_integration/services.ts | 15 -- .../ftr_provider_context.d.ts | 11 -- x-pack/test/oidc_api_integration/services.ts | 14 -- x-pack/test/pki_api_integration/apis/index.ts | 14 -- .../ftr_provider_context.d.ts | 11 -- x-pack/test/pki_api_integration/services.ts | 14 -- .../fixtures/kerberos}/README.md | 0 .../fixtures/kerberos}/kerberos_tools.ts | 0 .../fixtures/kerberos}/krb5.conf | 0 .../fixtures/kerberos}/krb5.keytab | Bin .../fixtures/oidc}/README.md | 0 .../fixtures/oidc}/jwks.json | 0 .../fixtures/oidc}/jwks_private.pem | 0 .../fixtures/oidc}/jwks_public.pem | 0 .../fixtures/oidc}/oidc_provider/kibana.json | 0 .../oidc}/oidc_provider/server/index.ts | 2 +- .../oidc}/oidc_provider/server/init_routes.ts | 2 +- .../fixtures/oidc}/oidc_tools.ts | 0 .../fixtures/pki}/README.md | 0 .../fixtures/pki}/first_client.p12 | Bin .../fixtures/pki}/kibana_ca.crt | 0 .../fixtures/pki}/kibana_ca.key | 0 .../fixtures/pki}/second_client.p12 | Bin .../fixtures/pki}/untrusted_client.p12 | Bin .../kerberos.config.ts} | 14 +- .../kerberos_anonymous_access.config.ts} | 4 +- .../login_selector.config.ts | 10 +- .../oidc.config.ts} | 8 +- .../oidc_implicit_flow.config.ts} | 6 +- .../pki.config.ts} | 6 +- .../test/security_api_integration/services.ts | 1 + .../tests/kerberos}/index.ts | 4 +- .../tests/kerberos}/kerberos_login.ts | 28 ++-- .../login_selector/basic_functionality.ts | 8 +- .../oidc/authorization_code_flow}/index.ts | 4 +- .../authorization_code_flow/oidc_auth.ts | 4 +- .../tests/oidc/implicit_flow}/index.ts | 4 +- .../tests/oidc}/implicit_flow/oidc_auth.ts | 4 +- .../tests/pki}/index.ts | 4 +- .../tests/pki}/pki_auth.ts | 66 +++++---- .../test/security_functional/oidc.config.ts | 7 +- 49 files changed, 271 insertions(+), 290 deletions(-) delete mode 100644 x-pack/test/kerberos_api_integration/apis/index.ts delete mode 100644 x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts delete mode 100644 x-pack/test/kerberos_api_integration/services.ts delete mode 100644 x-pack/test/oidc_api_integration/ftr_provider_context.d.ts delete mode 100644 x-pack/test/oidc_api_integration/services.ts delete mode 100644 x-pack/test/pki_api_integration/apis/index.ts delete mode 100644 x-pack/test/pki_api_integration/ftr_provider_context.d.ts delete mode 100644 x-pack/test/pki_api_integration/services.ts rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/README.md (100%) rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/kerberos_tools.ts (100%) rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/krb5.conf (100%) rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/krb5.keytab (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/README.md (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/jwks.json (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/jwks_private.pem (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/jwks_public.pem (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_provider/kibana.json (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_provider/server/index.ts (85%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_provider/server/init_routes.ts (98%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_tools.ts (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/README.md (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/first_client.p12 (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/kibana_ca.crt (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/kibana_ca.key (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/second_client.p12 (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/untrusted_client.p12 (100%) rename x-pack/test/{kerberos_api_integration/config.ts => security_api_integration/kerberos.config.ts} (82%) rename x-pack/test/{kerberos_api_integration/anonymous_access.config.ts => security_api_integration/kerberos_anonymous_access.config.ts} (87%) rename x-pack/test/{oidc_api_integration/config.ts => security_api_integration/oidc.config.ts} (89%) rename x-pack/test/{oidc_api_integration/implicit_flow.config.ts => security_api_integration/oidc_implicit_flow.config.ts} (88%) rename x-pack/test/{pki_api_integration/config.ts => security_api_integration/pki.config.ts} (93%) rename x-pack/test/{kerberos_api_integration/apis/security => security_api_integration/tests/kerberos}/index.ts (84%) rename x-pack/test/{kerberos_api_integration/apis/security => security_api_integration/tests/kerberos}/kerberos_login.ts (95%) rename x-pack/test/{oidc_api_integration/apis/implicit_flow => security_api_integration/tests/oidc/authorization_code_flow}/index.ts (73%) rename x-pack/test/{oidc_api_integration/apis => security_api_integration/tests/oidc}/authorization_code_flow/oidc_auth.ts (99%) rename x-pack/test/{oidc_api_integration/apis/authorization_code_flow => security_api_integration/tests/oidc/implicit_flow}/index.ts (74%) rename x-pack/test/{oidc_api_integration/apis => security_api_integration/tests/oidc}/implicit_flow/oidc_auth.ts (97%) rename x-pack/test/{pki_api_integration/apis/security => security_api_integration/tests/pki}/index.ts (85%) rename x-pack/test/{pki_api_integration/apis/security => security_api_integration/tests/pki}/pki_auth.ts (90%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2d70f6a97eed23..84076f4c4fbe93 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -254,9 +254,6 @@ /x-pack/test/ui_capabilities/ @elastic/kibana-security /x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security /x-pack/test/functional/apps/security/ @elastic/kibana-security -/x-pack/test/kerberos_api_integration/ @elastic/kibana-security -/x-pack/test/oidc_api_integration/ @elastic/kibana-security -/x-pack/test/pki_api_integration/ @elastic/kibana-security /x-pack/test/security_api_integration/ @elastic/kibana-security /x-pack/test/security_functional/ @elastic/kibana-security /x-pack/test/spaces_api_integration/ @elastic/kibana-security diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 4ea395e7b53de4..af26d1e60414a3 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -346,6 +346,16 @@ describe('KerberosAuthenticationProvider', () => { expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); + it('does not start SPNEGO for Ajax requests.', async () => { + const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }); + await expect(provider.authenticate(request)).resolves.toEqual( + AuthenticationResult.notHandled() + ); + + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + it('succeeds if state contains a valid token.', async () => { const user = mockAuthenticatedUser(); const request = httpServerMock.createKibanaRequest({ headers: {} }); @@ -442,9 +452,6 @@ describe('KerberosAuthenticationProvider', () => { }); it('fails with `Negotiate` challenge if both access and refresh tokens from the state are expired and backend supports Kerberos.', async () => { - const request = httpServerMock.createKibanaRequest(); - const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' }; - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( new (errors.AuthenticationException as any)('Unauthorized', { body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, @@ -456,37 +463,45 @@ describe('KerberosAuthenticationProvider', () => { mockOptions.tokens.refresh.mockResolvedValue(null); - await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( + const nonAjaxRequest = httpServerMock.createKibanaRequest(); + const nonAjaxTokenPair = { + accessToken: 'expired-token', + refreshToken: 'some-valid-refresh-token', + }; + await expect(provider.authenticate(nonAjaxRequest, nonAjaxTokenPair)).resolves.toEqual( AuthenticationResult.failed(failureReason, { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); - expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken); - }); - - it('does not re-start SPNEGO if both access and refresh tokens from the state are expired.', async () => { - const request = httpServerMock.createKibanaRequest({ routeAuthRequired: false }); - const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' }; - - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { - body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, + const ajaxRequest = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }); + const ajaxTokenPair = { + accessToken: 'expired-token', + refreshToken: 'ajax-some-valid-refresh-token', + }; + await expect(provider.authenticate(ajaxRequest, ajaxTokenPair)).resolves.toEqual( + AuthenticationResult.failed(failureReason, { + authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.tokens.refresh.mockResolvedValue(null); - - await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( - AuthenticationResult.notHandled() + const optionalAuthRequest = httpServerMock.createKibanaRequest({ routeAuthRequired: false }); + const optionalAuthTokenPair = { + accessToken: 'expired-token', + refreshToken: 'optional-some-valid-refresh-token', + }; + await expect( + provider.authenticate(optionalAuthRequest, optionalAuthTokenPair) + ).resolves.toEqual( + AuthenticationResult.failed(failureReason, { + authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, + }) ); - expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken); + expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(3); + expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(nonAjaxTokenPair.refreshToken); + expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(ajaxTokenPair.refreshToken); + expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(optionalAuthTokenPair.refreshToken); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 34ed9ac920e932..fa578b9dca45f9 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -13,6 +13,7 @@ import { import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; +import { canRedirectRequest } from '../can_redirect_request'; import { Tokens, TokenPair } from '../tokens'; import { BaseAuthenticationProvider } from './base'; @@ -32,8 +33,9 @@ const WWWAuthenticateHeaderName = 'WWW-Authenticate'; * @param request Request instance. */ function canStartNewSession(request: KibanaRequest) { - // We should try to establish new session only if request requires authentication. - return request.route.options.authRequired === true; + // We should try to establish new session only if request requires authentication and it's not an XHR request. + // Technically we can authenticate XHR requests too, but we don't want these to create a new session unintentionally. + return canRedirectRequest(request) && request.route.options.authRequired === true; } /** @@ -75,11 +77,8 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.notHandled(); } - let authenticationResult = authorizationHeader - ? await this.authenticateWithNegotiateScheme(request) - : AuthenticationResult.notHandled(); - - if (state && authenticationResult.notHandled()) { + let authenticationResult = AuthenticationResult.notHandled(); + if (state) { authenticationResult = await this.authenticateViaState(request, state); if ( authenticationResult.failed() && @@ -89,11 +88,15 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { } } - // If we couldn't authenticate by means of all methods above, let's try to check if Elasticsearch can - // start authentication mechanism negotiation, otherwise just return authentication result we have. - return authenticationResult.notHandled() && canStartNewSession(request) - ? await this.authenticateViaSPNEGO(request, state) - : authenticationResult; + if (!authenticationResult.notHandled() || !canStartNewSession(request)) { + return authenticationResult; + } + + // If we couldn't authenticate by means of all methods above, let's check if we're already at the authentication + // mechanism negotiation stage, otherwise check with Elasticsearch if we can start it. + return authorizationHeader + ? await this.authenticateWithNegotiateScheme(request) + : await this.authenticateViaSPNEGO(request, state); } /** @@ -264,12 +267,12 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.failed(err); } - // If refresh token is no longer valid, then we should clear session and renegotiate using SPNEGO. + // If refresh token is no longer valid, let's try to renegotiate new tokens using SPNEGO. We + // allow this because expired underlying token is an implementation detail and Kibana user + // facing session is still valid. if (refreshedTokenPair === null) { - this.logger.debug('Both access and refresh tokens are expired.'); - return canStartNewSession(request) - ? this.authenticateViaSPNEGO(request, state) - : AuthenticationResult.notHandled(); + this.logger.debug('Both access and refresh tokens are expired. Re-authenticating...'); + return this.authenticateViaSPNEGO(request, state); } try { diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 969682b225ceb4..94308ab5f24038 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -295,6 +295,22 @@ describe('PKIAuthenticationProvider', () => { expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); + it('does not exchange peer certificate to access token for Ajax requests.', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: { 'kbn-xsrf': 'xsrf' }, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + await expect(provider.authenticate(request)).resolves.toEqual( + AuthenticationResult.notHandled() + ); + + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + it('fails with non-401 error if state is available, peer is authorized, but certificate is not available.', async () => { const request = httpServerMock.createKibanaRequest({ socket: getMockSocket({ authorized: true }), @@ -383,14 +399,7 @@ describe('PKIAuthenticationProvider', () => { }); it('gets a new access token even if existing token is expired.', async () => { - const user = mockAuthenticatedUser(); - const request = httpServerMock.createKibanaRequest({ - socket: getMockSocket({ - authorized: true, - peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), - }), - }); - const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + const user = mockAuthenticatedUser({ authentication_provider: { type: 'pki', name: 'pki' } }); const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser @@ -399,55 +408,102 @@ describe('PKIAuthenticationProvider', () => { LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) ) // In response to a call with a new token. + .mockResolvedValueOnce(user) // In response to call with an expired token. + .mockRejectedValueOnce( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ) + // In response to a call with a new token. + .mockResolvedValueOnce(user) // In response to call with an expired token. + .mockRejectedValueOnce( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ) + // In response to a call with a new token. .mockResolvedValueOnce(user); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); - await expect(provider.authenticate(request, state)).resolves.toEqual( - AuthenticationResult.succeeded( - { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, - { - authHeaders: { authorization: 'Bearer access-token' }, - state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, - } - ) + const nonAjaxRequest = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + const nonAjaxState = { + accessToken: 'existing-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }; + await expect(provider.authenticate(nonAjaxRequest, nonAjaxState)).resolves.toEqual( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Bearer access-token' }, + state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, + }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { - body: { - x509_certificate_chain: [ - 'fingerprint:2A:7A:C2:DD:base64', - 'fingerprint:3B:8B:D3:EE:base64', - ], - }, + const ajaxRequest = httpServerMock.createKibanaRequest({ + headers: { 'kbn-xsrf': 'xsrf' }, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['3A:7A:C2:DD', '3B:8B:D3:EE']), + }), }); + const ajaxState = { + accessToken: 'existing-token', + peerCertificateFingerprint256: '3A:7A:C2:DD', + }; + await expect(provider.authenticate(ajaxRequest, ajaxState)).resolves.toEqual( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Bearer access-token' }, + state: { accessToken: 'access-token', peerCertificateFingerprint256: '3A:7A:C2:DD' }, + }) + ); - expect(request.headers).not.toHaveProperty('authorization'); - }); - - it('does not exchange peer certificate to a new access token even if existing token is expired and request does not require authentication.', async () => { - const request = httpServerMock.createKibanaRequest({ + const optionalAuthRequest = httpServerMock.createKibanaRequest({ routeAuthRequired: false, socket: getMockSocket({ authorized: true, - peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + peerCertificate: getMockPeerCertificate(['4A:7A:C2:DD', '3B:8B:D3:EE']), }), }); - const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValueOnce( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const optionalAuthState = { + accessToken: 'existing-token', + peerCertificateFingerprint256: '4A:7A:C2:DD', + }; + await expect(provider.authenticate(optionalAuthRequest, optionalAuthState)).resolves.toEqual( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Bearer access-token' }, + state: { accessToken: 'access-token', peerCertificateFingerprint256: '4A:7A:C2:DD' }, + }) ); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - await expect(provider.authenticate(request, state)).resolves.toEqual( - AuthenticationResult.notHandled() - ); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(3); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:2A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:3A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:4A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); - expect(request.headers).not.toHaveProperty('authorization'); + expect(nonAjaxRequest.headers).not.toHaveProperty('authorization'); + expect(ajaxRequest.headers).not.toHaveProperty('authorization'); + expect(optionalAuthRequest.headers).not.toHaveProperty('authorization'); }); it('fails with 401 if existing token is expired, but certificate is not present.', async () => { diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 9214a025484fea..3629a0ac34f02c 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -10,6 +10,7 @@ import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; +import { canRedirectRequest } from '../can_redirect_request'; import { Tokens } from '../tokens'; import { BaseAuthenticationProvider } from './base'; @@ -33,8 +34,9 @@ interface ProviderState { * @param request Request instance. */ function canStartNewSession(request: KibanaRequest) { - // We should try to establish new session only if request requires authentication. - return request.route.options.authRequired === true; + // We should try to establish new session only if request requires authentication and it's not an XHR request. + // Technically we can authenticate XHR requests too, but we don't want these to create a new session unintentionally. + return canRedirectRequest(request) && request.route.options.authRequired === true; } /** @@ -75,12 +77,14 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { authenticationResult = await this.authenticateViaState(request, state); // If access token expired or doesn't match to the certificate fingerprint we should try to get - // a new one in exchange to peer certificate chain assuming request can initiate new session. + // a new one in exchange to peer certificate chain. Since we know that we had a valid session + // before we can safely assume that it's desired to automatically re-create session even for XHR + // requests. const invalidAccessToken = authenticationResult.notHandled() || (authenticationResult.failed() && Tokens.isAccessTokenExpiredError(authenticationResult.error)); - if (invalidAccessToken && canStartNewSession(request)) { + if (invalidAccessToken) { authenticationResult = await this.authenticateViaPeerCertificate(request); // If we have an active session that we couldn't use to authenticate user and at the same time // we couldn't use peer's certificate to establish a new one, then we should respond with 401 @@ -88,14 +92,12 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { if (authenticationResult.notHandled()) { return AuthenticationResult.failed(Boom.unauthorized()); } - } else if (invalidAccessToken) { - return AuthenticationResult.notHandled(); } } - // If we couldn't authenticate by means of all methods above, let's try to check if we can authenticate - // request using its peer certificate chain, otherwise just return authentication result we have. - // We shouldn't establish new session if authentication isn't required for this particular request. + // If we couldn't authenticate by means of all methods above, let's check if the request is allowed + // to start a new session, and if so try to authenticate request using its peer certificate chain, + // otherwise just return authentication result we have. return authenticationResult.notHandled() && canStartNewSession(request) ? await this.authenticateViaPeerCertificate(request) : authenticationResult; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 1a7c0d52cad5ce..5e877717fd21e6 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -32,19 +32,19 @@ const onlyNotInCoverageTests = [ require.resolve('../test/detection_engine_api_integration/basic/config.ts'), require.resolve('../test/lists_api_integration/security_and_spaces/config.ts'), require.resolve('../test/plugin_api_integration/config.ts'), - require.resolve('../test/kerberos_api_integration/config.ts'), - require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'), require.resolve('../test/security_api_integration/saml.config.ts'), require.resolve('../test/security_api_integration/session_idle.config.ts'), require.resolve('../test/security_api_integration/session_lifespan.config.ts'), require.resolve('../test/security_api_integration/login_selector.config.ts'), require.resolve('../test/security_api_integration/audit.config.ts'), + require.resolve('../test/security_api_integration/kerberos.config.ts'), + require.resolve('../test/security_api_integration/kerberos_anonymous_access.config.ts'), + require.resolve('../test/security_api_integration/pki.config.ts'), + require.resolve('../test/security_api_integration/oidc.config.ts'), + require.resolve('../test/security_api_integration/oidc_implicit_flow.config.ts'), require.resolve('../test/token_api_integration/config.js'), - require.resolve('../test/oidc_api_integration/config.ts'), - require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'), require.resolve('../test/observability_api_integration/basic/config.ts'), require.resolve('../test/observability_api_integration/trial/config.ts'), - require.resolve('../test/pki_api_integration/config.ts'), require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'), require.resolve('../test/spaces_api_integration/spaces_only/config.ts'), require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'), diff --git a/x-pack/test/kerberos_api_integration/apis/index.ts b/x-pack/test/kerberos_api_integration/apis/index.ts deleted file mode 100644 index 17da3ea7acc8dd..00000000000000 --- a/x-pack/test/kerberos_api_integration/apis/index.ts +++ /dev/null @@ -1,14 +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 { FtrProviderContext } from '../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis Kerberos', function () { - this.tags('ciGroup6'); - loadTestFile(require.resolve('./security')); - }); -} diff --git a/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts b/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d7..00000000000000 --- a/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/kerberos_api_integration/services.ts b/x-pack/test/kerberos_api_integration/services.ts deleted file mode 100644 index dadae9c331a469..00000000000000 --- a/x-pack/test/kerberos_api_integration/services.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; - -export const services = { - ...commonServices, - legacyEs: apiIntegrationServices.legacyEs, - esSupertest: apiIntegrationServices.esSupertest, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, -}; diff --git a/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts b/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d7..00000000000000 --- a/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/oidc_api_integration/services.ts b/x-pack/test/oidc_api_integration/services.ts deleted file mode 100644 index e2abfa71451bcb..00000000000000 --- a/x-pack/test/oidc_api_integration/services.ts +++ /dev/null @@ -1,14 +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 { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; - -export const services = { - ...commonServices, - legacyEs: apiIntegrationServices.legacyEs, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, -}; diff --git a/x-pack/test/pki_api_integration/apis/index.ts b/x-pack/test/pki_api_integration/apis/index.ts deleted file mode 100644 index 01b537fc07d1bc..00000000000000 --- a/x-pack/test/pki_api_integration/apis/index.ts +++ /dev/null @@ -1,14 +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 { FtrProviderContext } from '../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis PKI', function () { - this.tags('ciGroup6'); - loadTestFile(require.resolve('./security')); - }); -} diff --git a/x-pack/test/pki_api_integration/ftr_provider_context.d.ts b/x-pack/test/pki_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d7..00000000000000 --- a/x-pack/test/pki_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/pki_api_integration/services.ts b/x-pack/test/pki_api_integration/services.ts deleted file mode 100644 index 73ec6fe3963920..00000000000000 --- a/x-pack/test/pki_api_integration/services.ts +++ /dev/null @@ -1,14 +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 { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; - -export const services = { - ...commonServices, - esSupertest: apiIntegrationServices.esSupertest, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, -}; diff --git a/x-pack/test/kerberos_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/kerberos/README.md similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/kerberos/README.md diff --git a/x-pack/test/kerberos_api_integration/fixtures/kerberos_tools.ts b/x-pack/test/security_api_integration/fixtures/kerberos/kerberos_tools.ts similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/kerberos_tools.ts rename to x-pack/test/security_api_integration/fixtures/kerberos/kerberos_tools.ts diff --git a/x-pack/test/kerberos_api_integration/fixtures/krb5.conf b/x-pack/test/security_api_integration/fixtures/kerberos/krb5.conf similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/krb5.conf rename to x-pack/test/security_api_integration/fixtures/kerberos/krb5.conf diff --git a/x-pack/test/kerberos_api_integration/fixtures/krb5.keytab b/x-pack/test/security_api_integration/fixtures/kerberos/krb5.keytab similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/krb5.keytab rename to x-pack/test/security_api_integration/fixtures/kerberos/krb5.keytab diff --git a/x-pack/test/oidc_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/oidc/README.md similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/oidc/README.md diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks.json b/x-pack/test/security_api_integration/fixtures/oidc/jwks.json similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks.json rename to x-pack/test/security_api_integration/fixtures/oidc/jwks.json diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks_private.pem b/x-pack/test/security_api_integration/fixtures/oidc/jwks_private.pem similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks_private.pem rename to x-pack/test/security_api_integration/fixtures/oidc/jwks_private.pem diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks_public.pem b/x-pack/test/security_api_integration/fixtures/oidc/jwks_public.pem similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks_public.pem rename to x-pack/test/security_api_integration/fixtures/oidc/jwks_public.pem diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/kibana.json similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/kibana.json diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts similarity index 85% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts index 58401e725830f4..082fec55c3413e 100644 --- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts +++ b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializer } from '../../../../../../src/core/server'; +import type { PluginInitializer } from '../../../../../../../src/core/server'; import { initRoutes } from './init_routes'; export const plugin: PluginInitializer = () => ({ diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts similarity index 98% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts index 73f92139806e3e..8f75246d995c3d 100644 --- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts +++ b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../../../../../src/core/server'; +import type { IRouter } from '../../../../../../../src/core/server'; import { createTokens } from '../../oidc_tools'; export function initRoutes(router: IRouter) { diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_tools.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_tools.ts similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/oidc_tools.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_tools.ts diff --git a/x-pack/test/pki_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/pki/README.md similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/pki/README.md diff --git a/x-pack/test/pki_api_integration/fixtures/first_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/first_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/first_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/first_client.p12 diff --git a/x-pack/test/pki_api_integration/fixtures/kibana_ca.crt b/x-pack/test/security_api_integration/fixtures/pki/kibana_ca.crt similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/kibana_ca.crt rename to x-pack/test/security_api_integration/fixtures/pki/kibana_ca.crt diff --git a/x-pack/test/pki_api_integration/fixtures/kibana_ca.key b/x-pack/test/security_api_integration/fixtures/pki/kibana_ca.key similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/kibana_ca.key rename to x-pack/test/security_api_integration/fixtures/pki/kibana_ca.key diff --git a/x-pack/test/pki_api_integration/fixtures/second_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/second_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/second_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/second_client.p12 diff --git a/x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/untrusted_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/untrusted_client.p12 diff --git a/x-pack/test/kerberos_api_integration/config.ts b/x-pack/test/security_api_integration/kerberos.config.ts similarity index 82% rename from x-pack/test/kerberos_api_integration/config.ts rename to x-pack/test/security_api_integration/kerberos.config.ts index 7b65d79e18e7dc..08c20079890833 100644 --- a/x-pack/test/kerberos_api_integration/config.ts +++ b/x-pack/test/security_api_integration/kerberos.config.ts @@ -11,21 +11,15 @@ import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); - const kerberosKeytabPath = resolve( - __dirname, - '../../test/kerberos_api_integration/fixtures/krb5.keytab' - ); - const kerberosConfigPath = resolve( - __dirname, - '../../test/kerberos_api_integration/fixtures/krb5.conf' - ); + const kerberosKeytabPath = resolve(__dirname, './fixtures/kerberos/krb5.keytab'); + const kerberosConfigPath = resolve(__dirname, './fixtures/kerberos/krb5.conf'); return { - testFiles: [require.resolve('./apis')], + testFiles: [require.resolve('./tests/kerberos')], servers: xPackAPITestsConfig.get('servers'), services, junit: { - reportName: 'X-Pack Kerberos API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (Kerberos)', }, esTestCluster: { diff --git a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts similarity index 87% rename from x-pack/test/kerberos_api_integration/anonymous_access.config.ts rename to x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts index 17362831a6cd0c..6621b536c7ca90 100644 --- a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts +++ b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts @@ -7,13 +7,13 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const kerberosAPITestsConfig = await readConfigFile(require.resolve('./config.ts')); + const kerberosAPITestsConfig = await readConfigFile(require.resolve('./kerberos.config.ts')); return { ...kerberosAPITestsConfig.getAll(), junit: { - reportName: 'X-Pack Kerberos API with Anonymous Access Integration Tests', + reportName: 'X-Pack Security API Integration Tests (Kerberos with Anonymous Access)', }, esTestCluster: { diff --git a/x-pack/test/security_api_integration/login_selector.config.ts b/x-pack/test/security_api_integration/login_selector.config.ts index 0e43715ba808e1..9688d42cb43617 100644 --- a/x-pack/test/security_api_integration/login_selector.config.ts +++ b/x-pack/test/security_api_integration/login_selector.config.ts @@ -15,13 +15,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); - const kerberosKeytabPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.keytab'); - const kerberosConfigPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.conf'); + const kerberosKeytabPath = resolve(__dirname, './fixtures/kerberos/krb5.keytab'); + const kerberosConfigPath = resolve(__dirname, './fixtures/kerberos/krb5.conf'); - const oidcJWKSPath = resolve(__dirname, '../oidc_api_integration/fixtures/jwks.json'); - const oidcIdPPlugin = resolve(__dirname, '../oidc_api_integration/fixtures/oidc_provider'); + const oidcJWKSPath = resolve(__dirname, './fixtures/oidc/jwks.json'); + const oidcIdPPlugin = resolve(__dirname, './fixtures/oidc/oidc_provider'); - const pkiKibanaCAPath = resolve(__dirname, '../pki_api_integration/fixtures/kibana_ca.crt'); + const pkiKibanaCAPath = resolve(__dirname, './fixtures/pki/kibana_ca.crt'); const saml1IdPMetadataPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); const saml2IdPMetadataPath = resolve(__dirname, './fixtures/saml/idp_metadata_2.xml'); diff --git a/x-pack/test/oidc_api_integration/config.ts b/x-pack/test/security_api_integration/oidc.config.ts similarity index 89% rename from x-pack/test/oidc_api_integration/config.ts rename to x-pack/test/security_api_integration/oidc.config.ts index 08aa0a6d9c0dde..cb92282b40d322 100644 --- a/x-pack/test/oidc_api_integration/config.ts +++ b/x-pack/test/security_api_integration/oidc.config.ts @@ -10,17 +10,17 @@ import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); - const plugin = resolve(__dirname, './fixtures/oidc_provider'); + const plugin = resolve(__dirname, './fixtures/oidc/oidc_provider'); const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); - const jwksPath = resolve(__dirname, './fixtures/jwks.json'); + const jwksPath = resolve(__dirname, './fixtures/oidc/jwks.json'); return { - testFiles: [require.resolve('./apis/authorization_code_flow')], + testFiles: [require.resolve('./tests/oidc/authorization_code_flow')], servers: xPackAPITestsConfig.get('servers'), security: { disableTestUser: true }, services, junit: { - reportName: 'X-Pack OpenID Connect API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (OIDC - Authorization Code Flow)', }, esTestCluster: { diff --git a/x-pack/test/oidc_api_integration/implicit_flow.config.ts b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts similarity index 88% rename from x-pack/test/oidc_api_integration/implicit_flow.config.ts rename to x-pack/test/security_api_integration/oidc_implicit_flow.config.ts index 992115d05c5a82..8907998f4df6d3 100644 --- a/x-pack/test/oidc_api_integration/implicit_flow.config.ts +++ b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts @@ -7,14 +7,14 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const oidcAPITestsConfig = await readConfigFile(require.resolve('./config.ts')); + const oidcAPITestsConfig = await readConfigFile(require.resolve('./oidc.config.ts')); return { ...oidcAPITestsConfig.getAll(), - testFiles: [require.resolve('./apis/implicit_flow')], + testFiles: [require.resolve('./tests/oidc/implicit_flow')], junit: { - reportName: 'X-Pack OpenID Connect API Integration Tests (Implicit Flow)', + reportName: 'X-Pack Security API Integration Tests (OIDC - Implicit Flow)', }, esTestCluster: { diff --git a/x-pack/test/pki_api_integration/config.ts b/x-pack/test/security_api_integration/pki.config.ts similarity index 93% rename from x-pack/test/pki_api_integration/config.ts rename to x-pack/test/security_api_integration/pki.config.ts index 5ce3111530dd99..1ce8bf9971fe0d 100644 --- a/x-pack/test/pki_api_integration/config.ts +++ b/x-pack/test/security_api_integration/pki.config.ts @@ -25,12 +25,12 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }; return { - testFiles: [require.resolve('./apis')], + testFiles: [require.resolve('./tests/pki')], servers, security: { disableTestUser: true }, services, junit: { - reportName: 'X-Pack PKI API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (PKI)', }, esTestCluster: { @@ -58,7 +58,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--server.ssl.certificate=${KBN_CERT_PATH}`, `--server.ssl.certificateAuthorities=${JSON.stringify([ CA_CERT_PATH, - resolve(__dirname, './fixtures/kibana_ca.crt'), + resolve(__dirname, './fixtures/pki/kibana_ca.crt'), ])}`, `--server.ssl.clientAuthentication=required`, `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, diff --git a/x-pack/test/security_api_integration/services.ts b/x-pack/test/security_api_integration/services.ts index a8d8048462693d..73ec6fe3963920 100644 --- a/x-pack/test/security_api_integration/services.ts +++ b/x-pack/test/security_api_integration/services.ts @@ -9,5 +9,6 @@ import { services as apiIntegrationServices } from '../api_integration/services' export const services = { ...commonServices, + esSupertest: apiIntegrationServices.esSupertest, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/kerberos_api_integration/apis/security/index.ts b/x-pack/test/security_api_integration/tests/kerberos/index.ts similarity index 84% rename from x-pack/test/kerberos_api_integration/apis/security/index.ts rename to x-pack/test/security_api_integration/tests/kerberos/index.ts index 77a053ab147485..3fa2d155353a78 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/index.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/index.ts @@ -7,7 +7,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('security', () => { + describe('security APIs - Kerberos', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./kerberos_login')); }); } diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts similarity index 95% rename from x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts rename to x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index c31f6b689e972c..e63f8cd2ebe32d 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { getMutualAuthenticationResponseToken, getSPNEGOToken, -} from '../../fixtures/kerberos_tools'; +} from '../../fixtures/kerberos/kerberos_tools'; export default function ({ getService }: FtrProviderContext) { const spnegoToken = getSPNEGOToken(); @@ -92,21 +92,21 @@ export default function ({ getService }: FtrProviderContext) { expect(spnegoResponse.headers['www-authenticate']).to.be('Negotiate'); }); - it('AJAX requests should properly initiate SPNEGO', async () => { + it('AJAX requests should not initiate SPNEGO', async () => { const ajaxResponse = await supertest .get('/abc/xyz/spnego?one=two three') .set('kbn-xsrf', 'xxx') .expect(401); expect(ajaxResponse.headers['set-cookie']).to.be(undefined); - expect(ajaxResponse.headers['www-authenticate']).to.be('Negotiate'); + expect(ajaxResponse.headers['www-authenticate']).to.be(undefined); }); }); describe('finishing SPNEGO', () => { it('should properly set cookie and authenticate user', async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -153,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) { it('should re-initiate SPNEGO handshake if token is rejected with 401', async () => { const spnegoResponse = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${Buffer.from('Hello').toString('base64')}`) .expect(401); expect(spnegoResponse.headers['set-cookie']).to.be(undefined); @@ -162,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) { it('should fail if SPNEGO token is rejected because of unknown reason', async () => { const spnegoResponse = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', 'Negotiate (:I am malformed:)') .expect(500); expect(spnegoResponse.headers['set-cookie']).to.be(undefined); @@ -175,7 +175,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -239,7 +239,7 @@ export default function ({ getService }: FtrProviderContext) { it('should redirect to `logged_out` page after successful logout', async () => { // First authenticate user to retrieve session cookie. const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -274,7 +274,9 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(apiResponse.headers['www-authenticate']).to.be('Negotiate'); + // Request with a session cookie that is linked to an invalidated/non-existent session is treated the same as + // request without any session cookie at all. + expect(apiResponse.headers['www-authenticate']).to.be(undefined); }); it('should redirect to home page if session cookie is not provided', async () => { @@ -290,7 +292,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -342,7 +344,7 @@ export default function ({ getService }: FtrProviderContext) { // This request should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const nonAjaxResponse = await supertest - .get('/app/kibana') + .get('/security/account') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -368,7 +370,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -405,7 +407,7 @@ export default function ({ getService }: FtrProviderContext) { it('non-AJAX call should initiate SPNEGO and clear existing cookie', async function () { const nonAjaxResponse = await supertest - .get('/') + .get('/security/account') .set('Cookie', sessionCookie.cookieString()) .expect(401); diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 432fd6ff912806..cf141972b044a1 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -11,11 +11,11 @@ import url from 'url'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; -import { getStateAndNonce } from '../../../oidc_api_integration/fixtures/oidc_tools'; +import { getStateAndNonce } from '../../fixtures/oidc/oidc_tools'; import { getMutualAuthenticationResponseToken, getSPNEGOToken, -} from '../../../kerberos_api_integration/fixtures/kerberos_tools'; +} from '../../fixtures/kerberos/kerberos_tools'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -29,9 +29,7 @@ export default function ({ getService }: FtrProviderContext) { const validPassword = kibanaServerConfig.password; const CA_CERT = readFileSync(CA_CERT_PATH); - const CLIENT_CERT = readFileSync( - resolve(__dirname, '../../../pki_api_integration/fixtures/first_client.p12') - ); + const CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/first_client.p12')); async function checkSessionCookie( sessionCookie: Cookie, diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts similarity index 73% rename from x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts rename to x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts index 0acae074f129f2..4def5388abae0c 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis', function () { + describe('security APIs - OIDC (Authorization Code Flow)', function () { this.tags('ciGroup6'); loadTestFile(require.resolve('./oidc_auth')); }); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts similarity index 99% rename from x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts rename to x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index 1fdb15a86ce0a2..aac41374734b24 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import request, { Cookie } from 'request'; import url from 'url'; import { delay } from 'bluebird'; -import { getStateAndNonce } from '../../fixtures/oidc_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { getStateAndNonce } from '../../../fixtures/oidc/oidc_tools'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts similarity index 74% rename from x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts rename to x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts index 0acae074f129f2..0441d14b9196de 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis', function () { + describe('security APIs - OIDC (Implicit Flow)', function () { this.tags('ciGroup6'); loadTestFile(require.resolve('./oidc_auth')); }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts similarity index 97% rename from x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts rename to x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts index 7c408d8b903e32..ced9598809e108 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import { JSDOM } from 'jsdom'; import request, { Cookie } from 'request'; import { format as formatURL } from 'url'; -import { createTokens, getStateAndNonce } from '../../fixtures/oidc_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { createTokens, getStateAndNonce } from '../../../fixtures/oidc/oidc_tools'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/pki_api_integration/apis/security/index.ts b/x-pack/test/security_api_integration/tests/pki/index.ts similarity index 85% rename from x-pack/test/pki_api_integration/apis/security/index.ts rename to x-pack/test/security_api_integration/tests/pki/index.ts index 63dca75d075fae..380335ba25f84b 100644 --- a/x-pack/test/pki_api_integration/apis/security/index.ts +++ b/x-pack/test/security_api_integration/tests/pki/index.ts @@ -7,7 +7,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('security', () => { + describe('security APIs - PKI', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./pki_auth')); }); } diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts similarity index 90% rename from x-pack/test/pki_api_integration/apis/security/pki_auth.ts rename to x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 43b728d12311d8..0331f756712cab 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -13,10 +13,10 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrProviderContext } from '../../ftr_provider_context'; const CA_CERT = readFileSync(CA_CERT_PATH); -const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/first_client.p12')); -const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/second_client.p12')); +const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/first_client.p12')); +const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/second_client.p12')); const UNTRUSTED_CLIENT_CERT = readFileSync( - resolve(__dirname, '../../fixtures/untrusted_client.p12') + resolve(__dirname, '../../fixtures/pki/untrusted_client.p12') ); export default function ({ getService }: FtrProviderContext) { @@ -97,9 +97,20 @@ export default function ({ getService }: FtrProviderContext) { // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); + it('AJAX requests should not create a new session', async () => { + const ajaxResponse = await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(401); + + expect(ajaxResponse.headers['set-cookie']).to.be(undefined); + }); + it('should properly set cookie and authenticate user', async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -110,35 +121,34 @@ export default function ({ getService }: FtrProviderContext) { const sessionCookie = request.cookie(cookies[0])!; checkCookieIsSet(sessionCookie); - expect(response.body).to.eql({ - username: 'first_client', - roles: ['kibana_admin'], - full_name: null, - email: null, - enabled: true, - metadata: { - pki_delegated_by_realm: 'reserved', - pki_delegated_by_user: 'kibana', - pki_dn: 'CN=first_client', - }, - authentication_realm: { name: 'pki1', type: 'pki' }, - lookup_realm: { name: 'pki1', type: 'pki' }, - authentication_provider: { name: 'pki', type: 'pki' }, - authentication_type: 'token', - }); - // Cookie should be accepted. await supertest .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) - .expect(200); + .expect(200, { + username: 'first_client', + roles: ['kibana_admin'], + full_name: null, + email: null, + enabled: true, + metadata: { + pki_delegated_by_realm: 'reserved', + pki_delegated_by_user: 'kibana', + pki_dn: 'CN=first_client', + }, + authentication_realm: { name: 'pki1', type: 'pki' }, + lookup_realm: { name: 'pki1', type: 'pki' }, + authentication_provider: { name: 'pki', type: 'pki' }, + authentication_type: 'token', + }); }); it('should update session if new certificate is provided', async () => { let response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -177,7 +187,7 @@ export default function ({ getService }: FtrProviderContext) { it('should reject valid cookie if used with untrusted certificate', async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -201,7 +211,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -274,7 +284,7 @@ export default function ({ getService }: FtrProviderContext) { it('should redirect to `logged_out` page after successful logout', async () => { // First authenticate user to retrieve session cookie. const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -317,7 +327,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -363,7 +373,7 @@ export default function ({ getService }: FtrProviderContext) { // This request should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const nonAjaxResponse = await supertest - .get('/app/kibana') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) diff --git a/x-pack/test/security_functional/oidc.config.ts b/x-pack/test/security_functional/oidc.config.ts index 1ed5d510984204..add6f0f164b3a0 100644 --- a/x-pack/test/security_functional/oidc.config.ts +++ b/x-pack/test/security_functional/oidc.config.ts @@ -20,8 +20,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ); const kibanaPort = kibanaFunctionalConfig.get('servers.kibana.port'); - const jwksPath = resolve(__dirname, '../oidc_api_integration/fixtures/jwks.json'); - const oidcOpPPlugin = resolve(__dirname, '../oidc_api_integration/fixtures/oidc_provider'); + const jwksPath = resolve(__dirname, '../security_api_integration/fixtures/oidc/jwks.json'); + const oidcOpPPlugin = resolve( + __dirname, + '../security_api_integration/fixtures/oidc/oidc_provider' + ); return { testFiles: [resolve(__dirname, './tests/oidc')], From 2c059575824c4aba9e49189d7ccbc89f10aa07b2 Mon Sep 17 00:00:00 2001 From: DeFazio Date: Mon, 9 Nov 2020 11:38:39 -0500 Subject: [PATCH 76/81] Update alert type selection layout to rows instead of grid (#73665) * Update layout to rows for alert types * Fix gutter usage * Update heading, remove icons * Non-working update to the combo box * Add incorrect updates with questions to fix * Fix combo box * Cleanup changes to specific to this module * fixed type checks and made combobox always visible * Added groups by producer * Added get producer name from kibana features names * Added search bar with list of alert types * Added search support functionality * fixed links to alert type * added alert type title * Fixed failing tests * Design updates to list * Remove unsed items in import list * fixed merge issue * Fixed due to comments * fixed tests * Design fixes Co-authored-by: Yuliia Naumenko --- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../public/application/app.tsx | 2 +- .../application/context/alerts_context.tsx | 2 + .../sections/alert_form/alert_add.test.tsx | 2 - .../sections/alert_form/alert_form.scss | 4 + .../sections/alert_form/alert_form.test.tsx | 4 +- .../sections/alert_form/alert_form.tsx | 293 ++++++++++++++---- .../sections/alert_form/solution_filter.tsx | 73 +++++ .../alerts_list/components/alerts_list.tsx | 1 + .../triggers_actions_ui/public/plugin.ts | 1 - 11 files changed, 318 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 485b24dced3463..d67cc064639427 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20148,7 +20148,6 @@ "xpack.triggersActionsUI.home.connectorsTabTitle": "コネクター", "xpack.triggersActionsUI.home.sectionDescription": "アラートを使用して条件を検出し、コネクターを使用してアクションを実行します。", "xpack.triggersActionsUI.managementSection.displayName": "アラートとアクション", - "xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel": "タグ (任意)", "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "ライセンスの管理", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存してテスト", @@ -20256,7 +20255,6 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "コネクターを作成する", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新規追加", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名前", - "xpack.triggersActionsUI.sections.alertForm.changeAlertTypeAriaLabel": "削除", "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "確認間隔", "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "条件を評価する頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "{actionTypeName}コネクターがありません", @@ -20273,7 +20271,6 @@ "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクションタイプを選択してください", - "xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "トリガータイプを選択してください", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "コネクターを読み込めません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 98d13011d3306f..103aef656c4e18 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20167,7 +20167,6 @@ "xpack.triggersActionsUI.home.connectorsTabTitle": "连接器", "xpack.triggersActionsUI.home.sectionDescription": "使用告警检测条件,并使用连接器采取操作。", "xpack.triggersActionsUI.managementSection.displayName": "告警和操作", - "xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel": "标记(可选)", "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "管理许可证", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存并测试", @@ -20276,7 +20275,6 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "创建连接器", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新添", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名称", - "xpack.triggersActionsUI.sections.alertForm.changeAlertTypeAriaLabel": "删除", "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "检查频率", "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "定义评估条件的频率。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "无 {actionTypeName} 连接器", @@ -20293,7 +20291,6 @@ "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "选择操作类型", - "xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "选择触发器类型", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "无法加载连接器", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index fc48a8e977c7da..5c1e0aa0100e89 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -16,8 +16,8 @@ import { CoreStart, ScopedHistory, } from 'kibana/public'; -import { Section, routeToAlertDetails } from './constants'; import { KibanaFeature } from '../../../features/common'; +import { Section, routeToAlertDetails } from './constants'; import { AppContextProvider } from './app_context'; import { ActionTypeRegistryContract, AlertTypeRegistryContract } from '../types'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index a4293f94268baa..0b2f777d13f256 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -18,6 +18,7 @@ import { DataPublicPluginStartUi, IndexPatternsContract, } from 'src/plugins/data/public'; +import { KibanaFeature } from '../../../../features/common'; import { AlertTypeRegistryContract, ActionTypeRegistryContract } from '../../types'; export interface AlertsContextValue> { @@ -34,6 +35,7 @@ export interface AlertsContextValue> { metadata?: MetaData; dataUi?: DataPublicPluginStartUi; dataIndexPatterns?: IndexPatternsContract; + kibanaFeatures?: KibanaFeature[]; } const AlertsContext = createContext(null as any); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index d66c5ba5121b83..4b5f8596501e14 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -182,8 +182,6 @@ describe('alert_add', () => { wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); - expect(wrapper.contains('Metadata: some value. Fields: test.')).toBeTruthy(); - expect(wrapper.find('input#alertName').props().value).toBe(''); expect(wrapper.find('[data-test-subj="tagsComboBox"]').first().text()).toBe(''); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss new file mode 100644 index 00000000000000..5d6ac684002fb2 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss @@ -0,0 +1,4 @@ +.triggersActionsUI__alertTypeNodeHeading { + margin-left: $euiSizeS; + margin-right: $euiSizeS; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 4041f6f451a23c..493b870a1a6d57 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -187,7 +187,7 @@ describe('alert_form', () => { it('renders alert type description', async () => { await setup(); - wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); + wrapper.find('button[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); const alertDescription = wrapper.find('[data-test-subj="alertDescription"]'); expect(alertDescription.exists()).toBeTruthy(); expect(alertDescription.first().text()).toContain('Alert when testing'); @@ -195,7 +195,7 @@ describe('alert_form', () => { it('renders alert type documentation link', async () => { await setup(); - wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); + wrapper.find('button[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); const alertDocumentationLink = wrapper.find('[data-test-subj="alertDocumentationLink"]'); expect(alertDocumentationLink.exists()).toBeTruthy(); expect(alertDocumentationLink.first().prop('href')).toBe('https://localhost.local/docs'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 20ad9a8d7c7014..213d1d7ad36df4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -9,15 +9,15 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, - EuiIcon, + EuiTextColor, EuiTitle, EuiForm, EuiSpacer, EuiFieldText, + EuiFieldSearch, EuiFlexGrid, EuiFormRow, EuiComboBox, - EuiKeyPadMenuItem, EuiFieldNumber, EuiSelect, EuiIconTip, @@ -25,11 +25,16 @@ import { EuiHorizontalRule, EuiLoadingSpinner, EuiEmptyPrompt, + EuiListGroupItem, + EuiListGroup, EuiLink, EuiText, + EuiNotificationBadge, } from '@elastic/eui'; import { some, filter, map, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; +import { capitalize } from 'lodash'; +import { KibanaFeature } from '../../../../../features/public'; import { getDurationNumberInItsUnit, getDurationUnitValue, @@ -37,12 +42,23 @@ import { import { loadAlertTypes } from '../../lib/alert_api'; import { actionVariablesFromAlertType } from '../../lib/action_variables'; import { AlertReducerAction } from './alert_reducer'; -import { AlertTypeModel, Alert, IErrorObject, AlertAction, AlertTypeIndex } from '../../../types'; +import { + AlertTypeModel, + Alert, + IErrorObject, + AlertAction, + AlertTypeIndex, + AlertType, +} from '../../../types'; import { getTimeOptions } from '../../../common/lib/get_time_options'; import { useAlertsContext } from '../../context/alerts_context'; import { ActionForm } from '../action_connector_form'; import { ALERTS_FEATURE_ID } from '../../../../../alerts/common'; import { hasAllPrivilege, hasShowActionsCapability } from '../../lib/capabilities'; +import { SolutionFilter } from './solution_filter'; +import './alert_form.scss'; + +const ENTER_KEY = 13; export function validateBaseProperties(alertObject: Alert) { const validationResult = { errors: {} }; @@ -77,6 +93,10 @@ export function validateBaseProperties(alertObject: Alert) { return validationResult; } +function getProducerFeatureName(producer: string, kibanaFeatures: KibanaFeature[]) { + return kibanaFeatures.find((featureItem) => featureItem.id === producer)?.name; +} + interface AlertFormProps { alert: Alert; dispatch: React.Dispatch; @@ -104,12 +124,12 @@ export const AlertForm = ({ actionTypeRegistry, docLinks, capabilities, + kibanaFeatures, } = alertsContext; const canShowActions = hasShowActionsCapability(capabilities); const [alertTypeModel, setAlertTypeModel] = useState(null); - const [alertTypesIndex, setAlertTypesIndex] = useState(undefined); const [alertInterval, setAlertInterval] = useState( alert.schedule.interval ? getDurationNumberInItsUnit(alert.schedule.interval) : undefined ); @@ -123,20 +143,53 @@ export const AlertForm = ({ alert.throttle ? getDurationUnitValue(alert.throttle) : 'm' ); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); + const [alertTypesIndex, setAlertTypesIndex] = useState(null); + + const [availableAlertTypes, setAvailableAlertTypes] = useState< + Array<{ alertTypeModel: AlertTypeModel; alertType: AlertType }> + >([]); + const [filteredAlertTypes, setFilteredAlertTypes] = useState< + Array<{ alertTypeModel: AlertTypeModel; alertType: AlertType }> + >([]); + const [searchText, setSearchText] = useState(); + const [inputText, setInputText] = useState(); + const [solutions, setSolutions] = useState | undefined>(undefined); + const [solutionsFilter, setSolutionFilter] = useState([]); // load alert types useEffect(() => { (async () => { try { - const alertTypes = await loadAlertTypes({ http }); + const alertTypesResult = await loadAlertTypes({ http }); const index: AlertTypeIndex = new Map(); - for (const alertTypeItem of alertTypes) { + for (const alertTypeItem of alertTypesResult) { index.set(alertTypeItem.id, alertTypeItem); } if (alert.alertTypeId && index.has(alert.alertTypeId)) { setDefaultActionGroupId(index.get(alert.alertTypeId)!.defaultActionGroupId); } setAlertTypesIndex(index); + const availableAlertTypesResult = getAvailableAlertTypes(alertTypesResult); + setAvailableAlertTypes(availableAlertTypesResult); + + const solutionsResult = availableAlertTypesResult.reduce( + (result: Map, alertTypeItem) => { + if (!result.has(alertTypeItem.alertType.producer)) { + result.set( + alertTypeItem.alertType.producer, + (kibanaFeatures + ? getProducerFeatureName(alertTypeItem.alertType.producer, kibanaFeatures) + : capitalize(alertTypeItem.alertType.producer)) ?? + capitalize(alertTypeItem.alertType.producer) + ); + } + return result; + }, + new Map() + ); + setSolutions( + new Map([...solutionsResult.entries()].sort(([, a], [, b]) => a.localeCompare(b))) + ); } catch (e) { toastNotifications.addDanger({ title: i18n.translate( @@ -184,47 +237,143 @@ export const AlertForm = ({ [dispatch] ); + useEffect(() => { + const searchValue = searchText ? searchText.trim().toLocaleLowerCase() : null; + setFilteredAlertTypes( + availableAlertTypes + .filter((alertTypeItem) => + solutionsFilter.length > 0 + ? solutionsFilter.find((item) => alertTypeItem.alertType!.producer === item) + : alertTypeItem + ) + .filter((alertTypeItem) => + searchValue + ? alertTypeItem.alertTypeModel.name + .toString() + .toLocaleLowerCase() + .includes(searchValue) || + alertTypeItem.alertType!.producer.toLocaleLowerCase().includes(searchValue) || + alertTypeItem.alertTypeModel.description.toLocaleLowerCase().includes(searchValue) + : alertTypeItem + ) + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alertTypeRegistry, availableAlertTypes, searchText, JSON.stringify(solutionsFilter)]); + + const getAvailableAlertTypes = (alertTypesResult: AlertType[]) => + alertTypeRegistry + .list() + .reduce( + ( + arr: Array<{ alertType: AlertType; alertTypeModel: AlertTypeModel }>, + alertTypeRegistryItem: AlertTypeModel + ) => { + const alertType = alertTypesResult.find((item) => alertTypeRegistryItem.id === item.id); + if (alertType) { + arr.push({ + alertType, + alertTypeModel: alertTypeRegistryItem, + }); + } + return arr; + }, + [] + ) + .filter((item) => item.alertType && hasAllPrivilege(alert, item.alertType)) + .filter((item) => + alert.consumer === ALERTS_FEATURE_ID + ? !item.alertTypeModel.requiresAppContext + : item.alertType!.producer === alert.consumer + ); + const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; const AlertParamsExpressionComponent = alertTypeModel ? alertTypeModel.alertParamsExpression : null; - const alertTypeRegistryList = alertTypesIndex - ? alertTypeRegistry - .list() - .filter( - (alertTypeRegistryItem: AlertTypeModel) => - alertTypesIndex.has(alertTypeRegistryItem.id) && - hasAllPrivilege(alert, alertTypesIndex.get(alertTypeRegistryItem.id)) - ) - .filter((alertTypeRegistryItem: AlertTypeModel) => - alert.consumer === ALERTS_FEATURE_ID - ? !alertTypeRegistryItem.requiresAppContext - : alertTypesIndex.get(alertTypeRegistryItem.id)!.producer === alert.consumer - ) - : []; + const alertTypesByProducer = filteredAlertTypes.reduce( + ( + result: Record>, + alertTypeValue + ) => { + const producer = alertTypeValue.alertType.producer; + if (producer) { + (result[producer] = result[producer] || []).push({ + name: + typeof alertTypeValue.alertTypeModel.name === 'string' + ? alertTypeValue.alertTypeModel.name + : alertTypeValue.alertTypeModel.name.props.defaultMessage, + id: alertTypeValue.alertTypeModel.id, + alertTypeItem: alertTypeValue.alertTypeModel, + }); + } + return result; + }, + {} + ); - const alertTypeNodes = alertTypeRegistryList.map(function (item, index) { - return ( - { - setAlertProperty('alertTypeId', item.id); - setActions([]); - setAlertTypeModel(item); - setAlertProperty('params', {}); - if (alertTypesIndex && alertTypesIndex.has(item.id)) { - setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId); - } - }} - > - - - ); - }); + const alertTypeNodes = Object.entries(alertTypesByProducer) + .sort(([a], [b]) => + solutions ? solutions.get(a)!.localeCompare(solutions.get(b)!) : a.localeCompare(b) + ) + .map(([solution, items], groupIndex) => ( + + + + + + {(kibanaFeatures + ? getProducerFeatureName(solution, kibanaFeatures) + : capitalize(solution)) ?? capitalize(solution)} + + + + + {items.length} + + + + + {items + .sort((a, b) => a.name.toString().localeCompare(b.name.toString())) + .map((item, index) => ( + + + {item.name} + +

{item.alertTypeItem.description}

+
+ + } + onClick={() => { + setAlertProperty('alertTypeId', item.id); + setActions([]); + setAlertTypeModel(item.alertTypeItem); + setAlertProperty('params', {}); + if (alertTypesIndex && alertTypesIndex.has(item.id)) { + setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId); + } + }} + /> +
+ ))} +
+ +
+ )); const alertTypeDetails = ( @@ -401,12 +550,9 @@ export const AlertForm = ({ {alertTypeModel ? ( {alertTypeDetails} - ) : alertTypeNodes.length ? ( + ) : availableAlertTypes.length ? ( - -
- -
-
+ +
+ +
+ + } + > + + + setInputText(e.target.value)} + onKeyUp={(e) => { + if (e.keyCode === ENTER_KEY) { + setSearchText(inputText); + } + }} + placeholder={i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.searchPlaceholderTitle', + { defaultMessage: 'Search' } + )} + /> + + {solutions ? ( + + setSolutionFilter(selectedSolutions)} + /> + + ) : null} + +
- - {alertTypeNodes} - + {alertTypeNodes}
) : alertTypesIndex ? ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx new file mode 100644 index 00000000000000..7caee22cf76330 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui'; + +interface SolutionFilterProps { + solutions: Map; + onChange?: (selectedSolutions: string[]) => void; +} + +export const SolutionFilter: React.FunctionComponent = ({ + solutions, + onChange, +}: SolutionFilterProps) => { + const [selectedValues, setSelectedValues] = useState([]); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + if (onChange) { + onChange(selectedValues); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedValues]); + + return ( + + setIsPopoverOpen(false)} + button={ + 0} + numActiveFilters={selectedValues.length} + numFilters={selectedValues.length} + onClick={() => setIsPopoverOpen(!isPopoverOpen)} + data-test-subj="solutionsFilterButton" + > + + + } + > +
+ {[...solutions.entries()].map(([id, title]) => ( + { + const isPreviouslyChecked = selectedValues.includes(id); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter((val) => val !== id)); + } else { + setSelectedValues([...selectedValues, id]); + } + }} + checked={selectedValues.includes(id) ? 'on' : undefined} + data-test-subj={`solution${id}FilterOption`} + > + {title} + + ))} +
+
+
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 3b0cd0b177b1bb..75f359888a8581 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -672,6 +672,7 @@ export const AlertsList: React.FunctionComponent = () => { capabilities, dataUi: dataPlugin.ui, dataIndexPatterns: dataPlugin.indexPatterns, + kibanaFeatures, }} > Date: Mon, 9 Nov 2020 10:41:04 -0600 Subject: [PATCH 77/81] [ML] Add option for anomaly charts for metric detector should plot min, mean or max as appropriate (#81662) --- .../anomalies_table/anomaly_details.js | 4 +- .../explorer_chart_config_builder.js | 6 ++ .../explorer_charts_container_service.js | 2 - .../routing/routes/timeseriesexplorer.tsx | 3 + .../services/ml_api_service/results.ts | 6 +- .../results_service/result_service_rx.ts | 21 ++++- .../results_service/results_service.d.ts | 3 +- .../results_service/results_service.js | 21 ++++- .../plot_function_controls/index.ts | 7 ++ .../plot_function_controls.tsx | 56 +++++++++++++ .../timeseries_chart/timeseries_chart.js | 16 ++++ .../timeseriesexplorer/get_criteria_fields.ts | 24 ++++++ .../get_function_description.ts | 62 ++++++++++++++ .../timeseries_search_service.ts | 5 +- .../timeseriesexplorer/timeseriesexplorer.js | 81 +++++++++++++++---- .../get_focus_data.ts | 18 ++++- .../timeseriesexplorer_utils.d.ts | 3 +- .../timeseriesexplorer_utils.js | 14 +++- .../models/results_service/results_service.ts | 10 ++- .../ml/server/routes/results_service.ts | 4 +- .../routes/schemas/results_service_schema.ts | 1 + 21 files changed, 333 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js index a2a3aea5988aa3..fdd855e80a6dfd 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js @@ -38,6 +38,7 @@ import { import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact'; import { formatValue } from '../../formatters/format_value'; import { MAX_CHARS } from './anomalies_table_constants'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; const TIME_FIELD_NAME = 'timestamp'; @@ -130,7 +131,8 @@ function getDetailsItems(anomaly, examples, filter) { title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.functionTitle', { defaultMessage: 'function', }), - description: source.function !== 'metric' ? source.function : source.function_description, + description: + source.function !== ML_JOB_AGGREGATION.METRIC ? source.function : source.function_description, }); if (source.field_name !== undefined) { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js index b75784c95c5209..2ba6e38081e6eb 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js @@ -13,6 +13,8 @@ import { parseInterval } from '../../../../common/util/parse_interval'; import { getEntityFieldList } from '../../../../common/util/anomaly_utils'; import { buildConfigFromDetector } from '../../util/chart_config_builder'; import { mlJobService } from '../../services/job_service'; +import { mlFunctionToESAggregation } from '../../../../common/util/job_utils'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; // Builds the chart configuration for the provided anomaly record, returning // an object with properties used for the display (series function and field, aggregation interval etc), @@ -48,6 +50,10 @@ export function buildConfig(record) { // define the metric series to be plotted. config.entityFields = getEntityFieldList(record); + if (record.function === ML_JOB_AGGREGATION.METRIC) { + config.metricFunction = mlFunctionToESAggregation(record.function_description); + } + // Build the tooltip data for the chart info icon, showing further details on what is being plotted. let functionLabel = config.metricFunction; if (config.metricFieldName !== undefined) { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index b9634f0eac3599..39166841a4e1bf 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -46,8 +46,6 @@ const ML_TIME_FIELD_NAME = 'timestamp'; const USE_OVERALL_CHART_LIMITS = false; const MAX_CHARTS_PER_ROW = 4; -// callback(getDefaultChartsData()); - export const anomalyDataChange = function ( chartsContainerWidth, anomalyRecords, diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index e4cf43ac917271..9331fdc04b7bb3 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -38,6 +38,7 @@ import { useResolver } from '../use_resolver'; import { basicResolvers } from '../resolvers'; import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { useTimefilter } from '../../contexts/kibana'; +import { useToastNotificationService } from '../../services/toast_notification_service'; export const timeSeriesExplorerRouteFactory = ( navigateToPath: NavigateToPath, @@ -88,6 +89,7 @@ export const TimeSeriesExplorerUrlStateManager: FC { + const toastNotificationService = useToastNotificationService(); const [appState, setAppState] = useUrlState('_a'); const [globalState, setGlobalState] = useUrlState('_g'); const [lastRefresh, setLastRefresh] = useState(0); @@ -293,6 +295,7 @@ export const TimeSeriesExplorerUrlStateManager: FC ({ latestMs: number, dateFormatTz: string, maxRecords: number, - maxExamples: number, - influencersFilterQuery: any + maxExamples?: number, + influencersFilterQuery?: any, + functionDescription?: string ) { const body = JSON.stringify({ jobIds, @@ -39,6 +40,7 @@ export const resultsApiProvider = (httpService: HttpService) => ({ maxRecords, maxExamples, influencersFilterQuery, + functionDescription, }); return httpService.http$({ diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts index 7c2c28fe9385c4..2869a7439614f6 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -19,6 +19,7 @@ import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; import { JobId } from '../../../../common/types/anomaly_detection_jobs'; import { MlApiServices } from '../ml_api_service'; import { CriteriaField } from './index'; +import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; interface ResultResponse { success: boolean; @@ -347,9 +348,10 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) { jobIds: string[], criteriaFields: CriteriaField[], threshold: any, - earliestMs: number, - latestMs: number, - maxResults: number | undefined + earliestMs: number | null, + latestMs: number | null, + maxResults: number | undefined, + functionDescription?: string ): Observable { const obj: RecordsForCriteria = { success: true, records: [] }; @@ -400,6 +402,19 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) { }); }); + if (functionDescription !== undefined) { + const mlFunctionToPlotIfMetric = + functionDescription !== undefined + ? aggregationTypeTransform.toML(functionDescription) + : functionDescription; + + boolCriteria.push({ + term: { + function_description: mlFunctionToPlotIfMetric, + }, + }); + } + return mlApiServices.results .anomalySearch$( { diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts index aae0cb51aa81da..962f384cf5b1b5 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts @@ -76,6 +76,7 @@ export function resultsServiceProvider( criteriaFields: any[], earliestMs: number, latestMs: number, - intervalMs: number + intervalMs: number, + actualPlotFunctionIfMetric?: string ): Promise; }; diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index 14a725c2e22b74..d053d69b4d1f2e 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -12,6 +12,7 @@ import { ANOMALY_SWIM_LANE_HARD_LIMIT, SWIM_LANE_DEFAULT_PAGE_SIZE, } from '../../explorer/explorer_constants'; +import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; /** * Service for carrying out Elasticsearch queries to obtain data for the Ml Results dashboards. @@ -1293,7 +1294,14 @@ export function resultsServiceProvider(mlApiServices) { // criteria, time range, and aggregation interval. // criteriaFields parameter must be an array, with each object in the array having 'fieldName' // 'fieldValue' properties. - getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, latestMs, intervalMs) { + getRecordMaxScoreByTime( + jobId, + criteriaFields, + earliestMs, + latestMs, + intervalMs, + actualPlotFunctionIfMetric + ) { return new Promise((resolve, reject) => { const obj = { success: true, @@ -1321,7 +1329,18 @@ export function resultsServiceProvider(mlApiServices) { }, }); }); + if (actualPlotFunctionIfMetric !== undefined) { + const mlFunctionToPlotIfMetric = + actualPlotFunctionIfMetric !== undefined + ? aggregationTypeTransform.toML(actualPlotFunctionIfMetric) + : actualPlotFunctionIfMetric; + mustCriteria.push({ + term: { + function_description: mlFunctionToPlotIfMetric, + }, + }); + } mlApiServices.results .anomalySearch( { diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts new file mode 100644 index 00000000000000..b8247eb91e1f5a --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/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 { PlotByFunctionControls } from './plot_function_controls'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx new file mode 100644 index 00000000000000..0356c20fecb9a3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const plotByFunctionOptions = [ + { + value: 'mean', + text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByAvgOptionLabel', { + defaultMessage: 'mean', + }), + }, + { + value: 'min', + text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByMinOptionLabel', { + defaultMessage: 'min', + }), + }, + { + value: 'max', + text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByMaxOptionLabel', { + defaultMessage: 'max', + }), + }, +]; +export const PlotByFunctionControls = ({ + functionDescription, + setFunctionDescription, +}: { + functionDescription: undefined | string; + setFunctionDescription: (func: string) => void; +}) => { + if (functionDescription === undefined) return null; + return ( + + + setFunctionDescription(e.target.value)} + aria-label={i18n.translate('xpack.ml.timeSeriesExplorer.metricPlotByOptionLabel', { + defaultMessage: 'Pick function to plot by (min, max, or average) if metric function', + })} + /> + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 3169ecfd1bbc76..8df186c5c3c6e6 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -1475,6 +1475,22 @@ class TimeseriesChartIntl extends Component { }); } + if (marker.metricFunction) { + tooltipData.push({ + label: i18n.translate( + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.metricActualPlotFunctionLabel', + { + defaultMessage: 'function', + } + ), + value: marker.metricFunction, + seriesIdentifier: { + key: seriesKey, + }, + valueAccessor: 'metric_function', + }); + } + if (modelPlotEnabled === false) { // Show actual/typical when available except for rare detectors. // Rare detectors always have 1 as actual and the probability as typical. diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts new file mode 100644 index 00000000000000..f9775976206c2e --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * Updates criteria fields for API calls, e.g. getAnomaliesTableData + * @param detectorIndex + * @param entities + */ +export const getCriteriaFields = (detectorIndex: number, entities: Record) => { + // Only filter on the entity if the field has a value. + const nonBlankEntities = entities.filter( + (entity: { fieldValue: any }) => entity.fieldValue !== null + ); + return [ + { + fieldName: 'detector_index', + fieldValue: detectorIndex, + }, + ...nonBlankEntities, + ]; +}; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts new file mode 100644 index 00000000000000..029e4645cfe268 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { mlResultsService } from '../services/results_service'; +import { ToastNotificationService } from '../services/toast_notification_service'; +import { getControlsForDetector } from './get_controls_for_detector'; +import { getCriteriaFields } from './get_criteria_fields'; +import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; +import { ML_JOB_AGGREGATION } from '../../../common/constants/aggregation_types'; + +/** + * Get the function description from the record with the highest anomaly score + */ +export const getFunctionDescription = async ( + { + selectedDetectorIndex, + selectedEntities, + selectedJobId, + selectedJob, + }: { + selectedDetectorIndex: number; + selectedEntities: Record; + selectedJobId: string; + selectedJob: CombinedJob; + }, + toastNotificationService: ToastNotificationService +) => { + // if the detector's function is metric, fetch the highest scoring anomaly record + // and set to plot the function_description (avg/min/max) of that record by default + if ( + selectedJob?.analysis_config?.detectors[selectedDetectorIndex]?.function !== + ML_JOB_AGGREGATION.METRIC + ) + return; + + const entityControls = getControlsForDetector( + selectedDetectorIndex, + selectedEntities, + selectedJobId + ); + const criteriaFields = getCriteriaFields(selectedDetectorIndex, entityControls); + try { + const resp = await mlResultsService + .getRecordsForCriteria([selectedJob.job_id], criteriaFields, 0, null, null, 1) + .toPromise(); + if (Array.isArray(resp?.records) && resp.records.length === 1) { + const highestScoringAnomaly = resp.records[0]; + return highestScoringAnomaly?.function_description; + } + } catch (error) { + toastNotificationService.displayErrorToast( + error, + i18n.translate('xpack.ml.timeSeriesExplorer.highestAnomalyScoreErrorToastTitle', { + defaultMessage: 'An error occurred getting record with the highest anomaly score', + }) + ); + } +}; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index e43ba8c87083a4..0d7abdab90be04 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -26,7 +26,8 @@ function getMetricData( entityFields: EntityField[], earliestMs: number, latestMs: number, - intervalMs: number + intervalMs: number, + esMetricFunction?: string ): Observable { if ( isModelPlotChartableForDetector(job, detectorIndex) && @@ -88,7 +89,7 @@ function getMetricData( chartConfig.datafeedConfig.indices, entityFields, chartConfig.datafeedConfig.query, - chartConfig.metricFunction, + esMetricFunction ?? chartConfig.metricFunction, chartConfig.metricFieldName, chartConfig.timeField, earliestMs, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 720c1377d4035d..e3b6e38f47babf 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -82,6 +82,9 @@ import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/ import { getControlsForDetector } from './get_controls_for_detector'; import { SeriesControls } from './components/series_controls'; import { TimeSeriesChartWithTooltips } from './components/timeseries_chart/timeseries_chart_with_tooltip'; +import { PlotByFunctionControls } from './components/plot_function_controls'; +import { aggregationTypeTransform } from '../../../common/util/anomaly_utils'; +import { getFunctionDescription } from './get_function_description'; // Used to indicate the chart is being plotted across // all partition field values, where the cardinality of the field cannot be @@ -140,6 +143,8 @@ function getTimeseriesexplorerDefaultState() { zoomTo: undefined, zoomFromFocusLoaded: undefined, zoomToFocusLoaded: undefined, + // Sets function to plot by if original function is metric + functionDescription: undefined, }; } @@ -217,6 +222,12 @@ export class TimeSeriesExplorer extends React.Component { }); }; + setFunctionDescription = (selectedFuction) => { + this.setState({ + functionDescription: selectedFuction, + }); + }; + previousChartProps = {}; previousShowAnnotations = undefined; previousShowForecast = undefined; @@ -270,7 +281,7 @@ export class TimeSeriesExplorer extends React.Component { */ getFocusData(selection) { const { selectedJobId, selectedForecastId, selectedDetectorIndex } = this.props; - const { modelPlotEnabled } = this.state; + const { modelPlotEnabled, functionDescription } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); @@ -292,6 +303,7 @@ export class TimeSeriesExplorer extends React.Component { entityControls.filter((entity) => entity.fieldValue !== null), searchBounds, selectedJob, + functionDescription, TIME_FIELD_NAME ); } @@ -322,6 +334,7 @@ export class TimeSeriesExplorer extends React.Component { tableInterval, tableSeverity, } = this.props; + const { functionDescription } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); @@ -335,7 +348,10 @@ export class TimeSeriesExplorer extends React.Component { earliestMs, latestMs, dateFormatTz, - ANOMALIES_TABLE_DEFAULT_QUERY_SIZE + ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, + undefined, + undefined, + functionDescription ) .pipe( map((resp) => { @@ -378,6 +394,24 @@ export class TimeSeriesExplorer extends React.Component { ); }; + getFunctionDescription = async () => { + const { selectedDetectorIndex, selectedEntities, selectedJobId } = this.props; + const selectedJob = mlJobService.getJob(selectedJobId); + + const functionDescriptionToPlot = await getFunctionDescription( + { + selectedDetectorIndex, + selectedEntities, + selectedJobId, + selectedJob, + }, + this.props.toastNotificationService + ); + if (!this.unmounted) { + this.setFunctionDescription(functionDescriptionToPlot); + } + }; + setForecastId = (forecastId) => { this.props.appStateHandler(APP_STATE_ACTION.SET_FORECAST_ID, forecastId); }; @@ -392,13 +426,13 @@ export class TimeSeriesExplorer extends React.Component { zoom, } = this.props; - const { loadCounter: currentLoadCounter } = this.state; + const { loadCounter: currentLoadCounter, functionDescription } = this.state; const currentSelectedJob = mlJobService.getJob(selectedJobId); - if (currentSelectedJob === undefined) { return; } + const functionToPlotByIfMetric = aggregationTypeTransform.toES(functionDescription); this.contextChartSelectedInitCallDone = false; @@ -533,7 +567,8 @@ export class TimeSeriesExplorer extends React.Component { nonBlankEntities, searchBounds.min.valueOf(), searchBounds.max.valueOf(), - stateUpdate.contextAggregationInterval.asMilliseconds() + stateUpdate.contextAggregationInterval.asMilliseconds(), + functionToPlotByIfMetric ) .toPromise() .then((resp) => { @@ -556,7 +591,8 @@ export class TimeSeriesExplorer extends React.Component { this.getCriteriaFields(detectorIndex, entityControls), searchBounds.min.valueOf(), searchBounds.max.valueOf(), - stateUpdate.contextAggregationInterval.asMilliseconds() + stateUpdate.contextAggregationInterval.asMilliseconds(), + functionToPlotByIfMetric ) .then((resp) => { const fullRangeRecordScoreData = processRecordScoreResults(resp.results); @@ -687,7 +723,6 @@ export class TimeSeriesExplorer extends React.Component { if (detectorId !== selectedDetectorIndex) { appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorId); } - // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. mlFieldFormatService.populateFormats([jobId]).catch((err) => { console.log('Error populating field formats:', err); @@ -810,7 +845,7 @@ export class TimeSeriesExplorer extends React.Component { this.componentDidUpdate(); } - componentDidUpdate(previousProps) { + componentDidUpdate(previousProps, previousState) { if (previousProps === undefined || previousProps.selectedJobId !== this.props.selectedJobId) { this.contextChartSelectedInitCallDone = false; this.setState({ fullRefresh: false, loading: true }, () => { @@ -818,6 +853,15 @@ export class TimeSeriesExplorer extends React.Component { }); } + if ( + previousProps === undefined || + previousProps.selectedJobId !== this.props.selectedJobId || + previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex || + !isEqual(previousProps.selectedEntities, this.props.selectedEntities) + ) { + this.getFunctionDescription(); + } + if ( previousProps === undefined || previousProps.selectedForecastId !== this.props.selectedForecastId @@ -840,7 +884,8 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || previousProps.selectedForecastId !== this.props.selectedForecastId || - previousProps.selectedJobId !== this.props.selectedJobId + previousProps.selectedJobId !== this.props.selectedJobId || + previousState.functionDescription !== this.state.functionDescription ) { const fullRefresh = previousProps === undefined || @@ -848,7 +893,8 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || previousProps.selectedForecastId !== this.props.selectedForecastId || - previousProps.selectedJobId !== this.props.selectedJobId; + previousProps.selectedJobId !== this.props.selectedJobId || + previousState.functionDescription !== this.state.functionDescription; this.loadSingleMetricData(fullRefresh); } @@ -919,8 +965,8 @@ export class TimeSeriesExplorer extends React.Component { zoomTo, zoomFromFocusLoaded, zoomToFocusLoaded, + functionDescription, } = this.state; - const chartProps = { modelPlotEnabled, contextChartData, @@ -939,7 +985,6 @@ export class TimeSeriesExplorer extends React.Component { zoomToFocusLoaded, autoZoomDuration, }; - const jobs = createTimeSeriesJobData(mlJobService.jobs); if (selectedDetectorIndex === undefined || mlJobService.getJob(selectedJobId) === undefined) { @@ -992,7 +1037,6 @@ export class TimeSeriesExplorer extends React.Component { )} - + {functionDescription && ( + + )} + {arePartitioningFieldsProvided && ( @@ -1014,7 +1068,6 @@ export class TimeSeriesExplorer extends React.Component { )} - {fullRefresh && loading === true && ( diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts index d1576be18d5bf0..044e5dfd6fe135 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts @@ -26,6 +26,7 @@ import { mlForecastService } from '../../services/forecast_service'; import { mlFunctionToESAggregation } from '../../../../common/util/job_utils'; import { GetAnnotationsResponse } from '../../../../common/types/annotations'; import { ANNOTATION_EVENT_USER } from '../../../../common/constants/annotations'; +import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; export interface Interval { asMilliseconds: () => number; @@ -51,8 +52,14 @@ export function getFocusData( modelPlotEnabled: boolean, nonBlankEntities: any[], searchBounds: any, - selectedJob: Job + selectedJob: Job, + functionDescription?: string | undefined ): Observable { + const esFunctionToPlotIfMetric = + functionDescription !== undefined + ? aggregationTypeTransform.toES(functionDescription) + : functionDescription; + return forkJoin([ // Query 1 - load metric data across selected time range. mlTimeSeriesSearchService.getMetricData( @@ -61,7 +68,8 @@ export function getFocusData( nonBlankEntities, searchBounds.min.valueOf(), searchBounds.max.valueOf(), - focusAggregationInterval.asMilliseconds() + focusAggregationInterval.asMilliseconds(), + esFunctionToPlotIfMetric ), // Query 2 - load all the records across selected time range for the chart anomaly markers. mlResultsService.getRecordsForCriteria( @@ -70,7 +78,8 @@ export function getFocusData( 0, searchBounds.min.valueOf(), searchBounds.max.valueOf(), - ANOMALIES_TABLE_DEFAULT_QUERY_SIZE + ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, + functionDescription ), // Query 3 - load any scheduled events for the selected job. mlResultsService.getScheduledEventsByBucket( @@ -143,7 +152,8 @@ export function getFocusData( focusChartData, anomalyRecords, focusAggregationInterval, - modelPlotEnabled + modelPlotEnabled, + functionDescription ); focusChartData = processScheduledEventsForChart(focusChartData, scheduledEvents); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts index 1b7a740d90dde1..4b101f888e4ea8 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts @@ -16,7 +16,8 @@ export function processDataForFocusAnomalies( chartData: any, anomalyRecords: any, aggregationInterval: any, - modelPlotEnabled: any + modelPlotEnabled: any, + functionDescription: any ): any; export function processScheduledEventsForChart(chartData: any, scheduledEvents: any): any; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js index d24794382128d5..5dc3a454e41e75 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js @@ -19,6 +19,7 @@ import { parseInterval } from '../../../../common/util/parse_interval'; import { getBoundsRoundedToInterval, getTimeBucketsFromCache } from '../../util/time_buckets'; import { CHARTS_POINT_TARGET, TIME_FIELD_NAME } from '../timeseriesexplorer_constants'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; // create new job objects based on standard job config objects // new job objects just contain job id, bucket span in seconds and a selected flag. @@ -100,7 +101,8 @@ export function processDataForFocusAnomalies( chartData, anomalyRecords, aggregationInterval, - modelPlotEnabled + modelPlotEnabled, + functionDescription ) { const timesToAddPointsFor = []; @@ -142,6 +144,12 @@ export function processDataForFocusAnomalies( // Look for a chart point with the same time as the record. // If none found, find closest time in chartData set. const recordTime = record[TIME_FIELD_NAME]; + if ( + record.function === ML_JOB_AGGREGATION.METRIC && + record.function_description !== functionDescription + ) + return; + const chartPoint = findChartPointForAnomalyTime(chartData, recordTime, aggregationInterval); if (chartPoint !== undefined) { // If chart aggregation interval > bucket span, there may be more than @@ -160,6 +168,10 @@ export function processDataForFocusAnomalies( chartPoint.value = record.actual; } + if (record.function === ML_JOB_AGGREGATION.METRIC) { + chartPoint.value = Array.isArray(record.actual) ? record.actual[0] : record.actual; + } + chartPoint.actual = record.actual; chartPoint.typical = record.typical; } else { diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index 53a35f63109787..a196f1034fdd35 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -54,7 +54,8 @@ export function resultsServiceProvider(mlClient: MlClient) { dateFormatTz: string, maxRecords: number = ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, maxExamples: number = DEFAULT_MAX_EXAMPLES, - influencersFilterQuery: any + influencersFilterQuery?: any, + functionDescription?: string ) { // Build the query to return the matching anomaly record results. // Add criteria for the time range, record score, plus any specified job IDs. @@ -102,6 +103,13 @@ export function resultsServiceProvider(mlClient: MlClient) { }, }); }); + if (functionDescription !== undefined) { + boolCriteria.push({ + term: { + function_description: functionDescription, + }, + }); + } if (influencersFilterQuery !== undefined) { boolCriteria.push(influencersFilterQuery); diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts index ce892b227c04ed..e708dd71043d0c 100644 --- a/x-pack/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -36,6 +36,7 @@ function getAnomaliesTableData(mlClient: MlClient, payload: any) { maxRecords, maxExamples, influencersFilterQuery, + functionDescription, } = payload; return rs.getAnomaliesTableData( jobIds, @@ -48,7 +49,8 @@ function getAnomaliesTableData(mlClient: MlClient, payload: any) { dateFormatTz, maxRecords, maxExamples, - influencersFilterQuery + influencersFilterQuery, + functionDescription ); } diff --git a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts index 5cd0ecdfbec90b..30a9054c69238c 100644 --- a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts @@ -26,6 +26,7 @@ export const anomaliesTableDataSchema = schema.object({ maxRecords: schema.number(), maxExamples: schema.maybe(schema.number()), influencersFilterQuery: schema.maybe(schema.any()), + functionDescription: schema.maybe(schema.nullable(schema.string())), }); export const categoryDefinitionSchema = schema.object({ From 1885dda6e6b567a81433fe574dd68d3057725da1 Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Mon, 9 Nov 2020 11:01:33 -0600 Subject: [PATCH 78/81] Fix test import objects (#82767) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/management/_import_objects.ts | 19 +++++++++++-------- x-pack/test/functional/config.js | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index 4a7a85c738fc2b..52428c944d666e 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -37,7 +37,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { - // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); await esArchiver.load('management'); @@ -471,16 +470,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should display an explicit error message when importing a file bigger than allowed', async () => { - await PageObjects.savedObjects.importFile( - path.join(__dirname, 'exports', '_import_too_big.ndjson') - ); + describe('when bigger than savedObjects.maxImportPayloadBytes (not Cloud)', function () { + // see --savedObjects.maxImportPayloadBytes in config file + this.tags(['skipCloud']); + it('should display an explicit error message when importing a file bigger than allowed', async () => { + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_too_big.ndjson') + ); - await PageObjects.savedObjects.checkImportError(); + await PageObjects.savedObjects.checkImportError(); - const errorText = await PageObjects.savedObjects.getImportErrorText(); + const errorText = await PageObjects.savedObjects.getImportErrorText(); - expect(errorText).to.contain(`Payload content length greater than maximum allowed`); + expect(errorText).to.contain(`Payload content length greater than maximum allowed`); + }); }); it('should display an explicit error message when importing an invalid file', async () => { diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 2072f4aa1c571c..11e4111696ccf9 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -88,6 +88,7 @@ export default async function ({ readConfigFile }) { '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', '--timelion.ui.enabled=true', + '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects ], }, uiSettings: { From e1b7073a643def8ff3bb7aa9254067957cbdf60f Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 9 Nov 2020 10:08:00 -0700 Subject: [PATCH 79/81] [Alerting][Connectors] Add new executor subaction to get 3rd party case fields (#82519) --- x-pack/plugins/actions/README.md | 21 +- .../builtin_action_types/jira/api.test.ts | 1478 ++++++++--------- .../server/builtin_action_types/jira/api.ts | 7 + .../server/builtin_action_types/jira/index.ts | 8 + .../server/builtin_action_types/jira/mocks.ts | 15 +- .../builtin_action_types/jira/schema.ts | 5 + .../builtin_action_types/jira/service.test.ts | 171 +- .../builtin_action_types/jira/service.ts | 55 +- .../server/builtin_action_types/jira/types.ts | 48 +- .../builtin_action_types/resilient/api.ts | 10 +- .../builtin_action_types/resilient/index.ts | 11 +- .../builtin_action_types/resilient/mocks.ts | 267 +++ .../builtin_action_types/resilient/schema.ts | 5 + .../resilient/service.test.ts | 45 +- .../builtin_action_types/resilient/service.ts | 21 +- .../builtin_action_types/resilient/types.ts | 35 +- .../servicenow/api.test.ts | 1215 +++++++------- .../builtin_action_types/servicenow/api.ts | 12 +- .../builtin_action_types/servicenow/index.ts | 18 +- .../builtin_action_types/servicenow/mocks.ts | 28 + .../builtin_action_types/servicenow/schema.ts | 6 + .../servicenow/service.test.ts | 39 +- .../servicenow/service.ts | 24 +- .../builtin_action_types/servicenow/types.ts | 30 +- .../actions/builtin_action_types/jira.ts | 10 +- .../actions/builtin_action_types/resilient.ts | 10 +- .../builtin_action_types/servicenow.ts | 10 +- 27 files changed, 2156 insertions(+), 1448 deletions(-) diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 4fef9bc582d088..432a4bfff7a6b9 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -69,18 +69,21 @@ Table of Contents - [`secrets`](#secrets-6) - [`params`](#params-6) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice) + - [`subActionParams (getFields)`](#subactionparams-getfields-1) - [Jira](#jira) - [`config`](#config-7) - [`secrets`](#secrets-7) - [`params`](#params-7) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1) - [`subActionParams (issueTypes)`](#subactionparams-issuetypes) + - [`subActionParams (getFields)`](#subactionparams-getfields-2) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2) - [IBM Resilient](#ibm-resilient) - [`config`](#config-8) - [`secrets`](#secrets-8) - [`params`](#params-8) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-3) + - [`subActionParams (getFields)`](#subactionparams-getfields-3) - [Command Line Utility](#command-line-utility) - [Developing New Action Types](#developing-new-action-types) - [licensing](#licensing) @@ -563,7 +566,7 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a | Property | Description | Type | | --------------- | ------------------------------------------------------------------------------------ | ------ | -| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string | +| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, and `getIncident` | string | | subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -580,6 +583,10 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a | urgency | The name of the urgency in ServiceNow. | string _(optional)_ | | impact | The name of the impact in ServiceNow. | string _(optional)_ | +#### `subActionParams (getFields)` + +No parameters for `getFields` sub-action. Provide an empty object `{}`. + --- ## Jira @@ -606,7 +613,7 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla | Property | Description | Type | | --------------- | ----------------------------------------------------------------------------------------------------------------------- | ------ | -| subAction | The sub action to perform. It can be `pushToService`, `handshake`, `getIncident`, `issueTypes`, and `fieldsByIssueType` | string | +| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, `getIncident`, `issueTypes`, and `fieldsByIssueType` | string | | subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -627,6 +634,10 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla No parameters for `issueTypes` sub-action. Provide an empty object `{}`. +#### `subActionParams (getFields)` + +No parameters for `getFields` sub-action. Provide an empty object `{}`. + #### `subActionParams (pushToService)` | Property | Description | Type | @@ -655,7 +666,7 @@ ID: `.resilient` | Property | Description | Type | | --------------- | ------------------------------------------------------------------------------------ | ------ | -| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string | +| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, and `getIncident` | string | | subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -670,6 +681,10 @@ ID: `.resilient` | incidentTypes | An array with the ids of IBM Resilient incident types. | number[] _(optional)_ | | severityCode | IBM Resilient id of the severity code. | number _(optional)_ | +#### `subActionParams (getFields)` + +No parameters for `getFields` sub-action. Provide an empty object `{}`. + # Command Line Utility The [`kbn-action`](https://github.com/pmuellr/kbn-action) tool can be used to send HTTP requests to the Actions plugin. For instance, to create a Slack action from the `.slack` Action Type, use the following command: diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts index e8fa9f76df7780..5a7617ada1bf02 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts @@ -15,804 +15,792 @@ describe('api', () => { beforeEach(() => { externalService = externalServiceMock.create(); - jest.clearAllMocks(); }); - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('pushToService', () => { - describe('create incident - cases', () => { - test('it creates an incident', async () => { - const params = { ...apiParams, externalId: null }; - const res = await api.pushToService({ - externalService, - mapping, - params, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - ], - }); - }); - - test('it creates an incident without comments', async () => { - const params = { ...apiParams, externalId: null, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - }); - }); - - test('it calls createIncident correctly', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - - expect(externalService.createIncident).toHaveBeenCalledWith({ - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'Incident description (created at 2020-04-27T10:59:46.202Z by Elastic User)', - summary: 'Incident title (created at 2020-04-27T10:59:46.202Z by Elastic User)', - }, - }); - expect(externalService.updateIncident).not.toHaveBeenCalled(); - }); - - test('it calls createIncident correctly without mapping', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - - expect(externalService.createIncident).toHaveBeenCalledWith({ - incident: { - description: 'Incident description', - summary: 'Incident title', - issueType: '10006', - labels: ['kibana', 'elastic'], - priority: 'High', - parent: null, - }, - }); - expect(externalService.updateIncident).not.toHaveBeenCalled(); + describe('create incident - cases', () => { + test('it creates an incident', async () => { + const params = { ...apiParams, externalId: null }; + const res = await api.pushToService({ + externalService, + mapping, + params, + logger: mockedLogger, }); - test('it calls createComment correctly', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', + comments: [ + { commentId: 'case-comment-1', - comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + pushedDate: '2020-04-27T10:59:46.202Z', }, - }); - - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { + { commentId: 'case-comment-2', - comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + pushedDate: '2020-04-27T10:59:46.202Z', }, - }); + ], }); + }); - test('it calls createComment correctly without mapping', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-1', - comment: 'A comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - }, - }); + test('it creates an incident without comments', async () => { + const params = { ...apiParams, externalId: null, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + logger: mockedLogger, + }); - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-2', - comment: 'Another comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - }, - }); + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', }); }); - describe('update incident', () => { - test('it updates an incident', async () => { - const res = await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - ], - }); - }); - - test('it updates an incident without comments', async () => { - const params = { ...apiParams, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - }); - }); - - test('it calls updateIncident correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + test('it calls createIncident correctly', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + + expect(externalService.createIncident).toHaveBeenCalledWith({ + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: 'Incident description (created at 2020-04-27T10:59:46.202Z by Elastic User)', + summary: 'Incident title (created at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + expect(externalService.updateIncident).not.toHaveBeenCalled(); + }); + + test('it calls createIncident correctly without mapping', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + + expect(externalService.createIncident).toHaveBeenCalledWith({ + incident: { + description: 'Incident description', + summary: 'Incident title', + issueType: '10006', + labels: ['kibana', 'elastic'], + priority: 'High', + parent: null, + }, + }); + expect(externalService.updateIncident).not.toHaveBeenCalled(); + }); + + test('it calls createComment correctly', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - expect(externalService.createIncident).not.toHaveBeenCalled(); - }); - - test('it calls updateIncident correctly without mapping', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - description: 'Incident description', - summary: 'Incident title', - issueType: '10006', - labels: ['kibana', 'elastic'], - priority: 'High', - parent: null, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - expect(externalService.createIncident).not.toHaveBeenCalled(); + }, }); - test('it calls createComment correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-1', - comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-2', - comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); + }, }); + }); - test('it calls createComment correctly without mapping', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-1', - comment: 'A comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + test('it calls createComment correctly without mapping', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', + }, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); + }, + }); - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-2', - comment: 'Another comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', + }, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); + }, }); }); + }); - describe('issueTypes', () => { - test('it returns the issue types correctly', async () => { - const res = await api.issueTypes({ - externalService, - params: {}, - }); - expect(res).toEqual([ + describe('update incident', () => { + test('it updates an incident', async () => { + const res = await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', + comments: [ { - id: '10006', - name: 'Task', + commentId: 'case-comment-1', + pushedDate: '2020-04-27T10:59:46.202Z', }, { - id: '10007', - name: 'Bug', + commentId: 'case-comment-2', + pushedDate: '2020-04-27T10:59:46.202Z', }, - ]); + ], }); }); - describe('fieldsByIssueType', () => { - test('it returns the fields correctly', async () => { - const res = await api.fieldsByIssueType({ - externalService, - params: { id: '10006' }, - }); - expect(res).toEqual({ - summary: { allowedValues: [], defaultValue: {} }, - priority: { - allowedValues: [ - { - name: 'Medium', - id: '3', - }, - ], - defaultValue: { name: 'Medium', id: '3' }, - }, - }); + test('it updates an incident without comments', async () => { + const params = { ...apiParams, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', }); }); - describe('getIssues', () => { - test('it returns the issues correctly', async () => { - const res = await api.issues({ - externalService, - params: { title: 'Title test' }, - }); - expect(res).toEqual([ - { - id: '10267', - key: 'RJ-107', - title: 'Test title', - }, - ]); + test('it calls updateIncident correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, }); + expect(externalService.createIncident).not.toHaveBeenCalled(); }); - describe('getIssue', () => { - test('it returns the issue correctly', async () => { - const res = await api.issue({ - externalService, - params: { id: 'RJ-107' }, - }); - expect(res).toEqual({ - id: '10267', - key: 'RJ-107', - title: 'Test title', - }); + test('it calls updateIncident correctly without mapping', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + description: 'Incident description', + summary: 'Incident title', + issueType: '10006', + labels: ['kibana', 'elastic'], + priority: 'High', + parent: null, + }, }); + expect(externalService.createIncident).not.toHaveBeenCalled(); }); - describe('mapping variations', () => { - test('overwrite & append', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - }, - }); - }); - - test('nothing & append', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + test('it calls createComment correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('append & append', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: - 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('nothing & nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, + }, + }); + + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('overwrite & nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('overwrite & overwrite', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('it calls createComment correctly without mapping', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('nothing & overwrite', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('append & overwrite', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: - 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('append & nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: - 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('comment nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'nothing', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.createComment).not.toHaveBeenCalled(); + }, + }); + }); + }); + + describe('issueTypes', () => { + test('it returns the issue types correctly', async () => { + const res = await api.issueTypes({ + externalService, + params: {}, + }); + expect(res).toEqual([ + { + id: '10006', + name: 'Task', + }, + { + id: '10007', + name: 'Bug', + }, + ]); + }); + }); + + describe('fieldsByIssueType', () => { + test('it returns the fields correctly', async () => { + const res = await api.fieldsByIssueType({ + externalService, + params: { id: '10006' }, + }); + expect(res).toEqual({ + summary: { allowedValues: [], defaultValue: {} }, + priority: { + allowedValues: [ + { + name: 'Medium', + id: '3', + }, + ], + defaultValue: { name: 'Medium', id: '3' }, + }, + }); + }); + }); + + describe('getIssues', () => { + test('it returns the issues correctly', async () => { + const res = await api.issues({ + externalService, + params: { title: 'Title test' }, + }); + expect(res).toEqual([ + { + id: '10267', + key: 'RJ-107', + title: 'Test title', + }, + ]); + }); + }); + + describe('getIssue', () => { + test('it returns the issue correctly', async () => { + const res = await api.issue({ + externalService, + params: { id: 'RJ-107' }, + }); + expect(res).toEqual({ + id: '10267', + key: 'RJ-107', + title: 'Test title', + }); + }); + }); + + describe('mapping variations', () => { + test('overwrite & append', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: + 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('nothing & append', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: + 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('append & append', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: + 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: + 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('nothing & nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + }, + }); + }); + + test('overwrite & nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('overwrite & overwrite', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('nothing & overwrite', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('append & overwrite', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: + 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('append & nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: + 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('comment nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'nothing', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, }); + expect(externalService.createComment).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts index 679c1541964ce5..feeb69b1d1a0ec 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts @@ -17,6 +17,7 @@ import { PushToServiceApiParams, PushToServiceResponse, GetIssueHandlerArgs, + GetCommonFieldsHandlerArgs, } from './types'; // TODO: to remove, need to support Case @@ -39,6 +40,11 @@ const getIssueTypesHandler = async ({ externalService }: GetIssueTypesHandlerArg return res; }; +const getFieldsHandler = async ({ externalService }: GetCommonFieldsHandlerArgs) => { + const res = await externalService.getFields(); + return res; +}; + const getFieldsByIssueTypeHandler = async ({ externalService, params, @@ -157,6 +163,7 @@ const pushToServiceHandler = async ({ }; export const api: ExternalServiceApi = { + getFields: getFieldsHandler, handshake: handshakeHandler, pushToService: pushToServiceHandler, getIncident: getIncidentHandler, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts index 9d6ff90c337009..c70c0810926f48 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts @@ -40,6 +40,7 @@ interface GetActionTypeParams { } const supportedSubActions: string[] = [ + 'getFields', 'pushToService', 'issueTypes', 'fieldsByIssueType', @@ -145,6 +146,13 @@ async function executor( }); } + if (subAction === 'getFields') { + data = await api.getFields({ + externalService, + params: subActionParams, + }); + } + if (subAction === 'issues') { const getIssuesParams = subActionParams as ExecutorSubActionGetIssuesParams; data = await api.issues({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts index b98eda799e3aad..87a0f156a0c2a9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts @@ -73,6 +73,20 @@ const createMock = (): jest.Mocked => { key: 'RJ-107', title: 'Test title', })), + getFields: jest.fn().mockImplementation(() => ({ + description: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + summary: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + })), }; service.createComment.mockImplementationOnce(() => @@ -97,7 +111,6 @@ const createMock = (): jest.Mocked => { const externalServiceMock = { create: createMock, }; - const mapping: Map> = new Map(); mapping.set('title', { diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts index 513ca2cf18e6c7..70b60ada9c3864 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts @@ -55,6 +55,7 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ }); // Reserved for future implementation +export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); export const ExecutorSubActionGetCapabilitiesParamsSchema = schema.object({}); export const ExecutorSubActionGetIssueTypesParamsSchema = schema.object({}); @@ -65,6 +66,10 @@ export const ExecutorSubActionGetIssuesParamsSchema = schema.object({ title: sch export const ExecutorSubActionGetIssueParamsSchema = schema.object({ id: schema.string() }); export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), schema.object({ subAction: schema.literal('getIncident'), subActionParams: ExecutorSubActionGetIncidentParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts index fe4e135c76fc37..2165ba56428c94 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts @@ -57,8 +57,10 @@ const fieldsResponse = { id: '10006', name: 'Task', fields: { - summary: { fieldId: 'summary' }, + summary: { required: true, schema: { type: 'string' }, fieldId: 'summary' }, priority: { + required: false, + schema: { type: 'string' }, fieldId: 'priority', allowedValues: [ { @@ -198,7 +200,7 @@ describe('Jira service', () => { error.response = { data: { errors: { summary: 'Required field' } } }; throw error; }); - expect(service.getIncident('1')).rejects.toThrow( + await expect(service.getIncident('1')).rejects.toThrow( '[Action][Jira]: Unable to get incident with id 1. Error: An error has occurred Reason: Required field' ); }); @@ -348,7 +350,7 @@ describe('Jira service', () => { throw error; }); - expect( + await expect( service.createIncident({ incident: { summary: 'title', @@ -442,7 +444,7 @@ describe('Jira service', () => { throw error; }); - expect( + await expect( service.updateIncident({ incidentId: '1', incident: { @@ -526,7 +528,7 @@ describe('Jira service', () => { throw error; }); - expect( + await expect( service.createComment({ incidentId: '1', comment: { @@ -587,7 +589,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getCapabilities()).rejects.toThrow( + await expect(service.getCapabilities()).rejects.toThrow( '[Action][Jira]: Unable to get capabilities. Error: An error has occurred. Reason: Could not get capabilities' ); }); @@ -657,7 +659,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssueTypes()).rejects.toThrow( + await expect(service.getIssueTypes()).rejects.toThrow( '[Action][Jira]: Unable to get issue types. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -741,7 +743,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssueTypes()).rejects.toThrow( + await expect(service.getIssueTypes()).rejects.toThrow( '[Action][Jira]: Unable to get issue types. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -765,6 +767,8 @@ describe('Jira service', () => { expect(res).toEqual({ priority: { + required: false, + schema: { type: 'string' }, allowedValues: [ { id: '1', name: 'Highest' }, { id: '2', name: 'High' }, @@ -774,7 +778,12 @@ describe('Jira service', () => { ], defaultValue: { id: '3', name: 'Medium' }, }, - summary: { allowedValues: [], defaultValue: {} }, + summary: { + required: true, + schema: { type: 'string' }, + allowedValues: [], + defaultValue: {}, + }, }); }); @@ -815,7 +824,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getFieldsByIssueType('10006')).rejects.toThrow( + await expect(service.getFieldsByIssueType('10006')).rejects.toThrow( '[Action][Jira]: Unable to get fields. Error: An error has occurred. Reason: Could not get fields' ); }); @@ -837,8 +846,10 @@ describe('Jira service', () => { requestMock.mockImplementationOnce(() => ({ data: { values: [ - { fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, { + required: false, + schema: { type: 'string' }, fieldId: 'priority', allowedValues: [ { @@ -859,10 +870,17 @@ describe('Jira service', () => { expect(res).toEqual({ priority: { + required: false, + schema: { type: 'string' }, allowedValues: [{ id: '3', name: 'Medium' }], defaultValue: { id: '3', name: 'Medium' }, }, - summary: { allowedValues: [], defaultValue: {} }, + summary: { + required: true, + schema: { type: 'string' }, + allowedValues: [], + defaultValue: {}, + }, }); }); @@ -881,8 +899,10 @@ describe('Jira service', () => { requestMock.mockImplementationOnce(() => ({ data: { values: [ - { fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, { + required: true, + schema: { type: 'string' }, fieldId: 'priority', allowedValues: [ { @@ -927,7 +947,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getFieldsByIssueType('10006')).rejects.toThrow( + await expect(service.getFieldsByIssueType('10006')).rejects.toThrowError( '[Action][Jira]: Unable to get fields. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -976,7 +996,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssues('Test title')).rejects.toThrow( + await expect(service.getIssues('Test title')).rejects.toThrow( '[Action][Jira]: Unable to get issues. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -1020,9 +1040,128 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssue('RJ-107')).rejects.toThrow( + await expect(service.getIssue('RJ-107')).rejects.toThrow( '[Action][Jira]: Unable to get issue with id RJ-107. Error: An error has occurred. Reason: Could not get issue types' ); }); }); + + describe('getFields', () => { + const callMocks = () => { + requestMock + .mockImplementationOnce(() => ({ + data: { + capabilities: { + 'list-project-issuetypes': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes', + 'list-issuetype-fields': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields', + }, + }, + })) + .mockImplementationOnce(() => ({ + data: { + values: issueTypesResponse.data.projects[0].issuetypes, + }, + })) + .mockImplementationOnce(() => ({ + data: { + capabilities: { + 'list-project-issuetypes': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes', + 'list-issuetype-fields': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields', + }, + }, + })) + .mockImplementationOnce(() => ({ + data: { + capabilities: { + 'list-project-issuetypes': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes', + 'list-issuetype-fields': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields', + }, + }, + })) + .mockImplementationOnce(() => ({ + data: { + values: [ + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'description' }, + { + required: false, + schema: { type: 'string' }, + fieldId: 'priority', + allowedValues: [ + { + name: 'Medium', + id: '3', + }, + ], + defaultValue: { + name: 'Medium', + id: '3', + }, + }, + ], + }, + })) + .mockImplementationOnce(() => ({ + data: { + values: [ + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'description' }, + ], + }, + })); + }; + beforeEach(() => { + jest.resetAllMocks(); + }); + test('it should call request with correct arguments', async () => { + callMocks(); + await service.getFields(); + const callUrls = [ + 'https://siem-kibana.atlassian.net/rest/capabilities', + 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes', + 'https://siem-kibana.atlassian.net/rest/capabilities', + 'https://siem-kibana.atlassian.net/rest/capabilities', + 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10006', + 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10007', + ]; + requestMock.mock.calls.forEach((call, i) => { + expect(call[0].url).toEqual(callUrls[i]); + }); + }); + test('it returns common fields correctly', async () => { + callMocks(); + const res = await service.getFields(); + expect(res).toEqual({ + description: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + summary: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + }); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + const error: ResponseError = new Error('An error has occurred'); + error.response = { data: { errors: { summary: 'Required field' } } }; + throw error; + }); + await expect(service.getFields()).rejects.toThrow( + '[Action][Jira]: Unable to get capabilities. Error: An error has occurred. Reason: Required field' + ); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts index f5347891f4f705..b3c5bb4a84de5b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts @@ -8,18 +8,20 @@ import axios from 'axios'; import { Logger } from '../../../../../../src/core/server'; import { - ExternalServiceCredentials, - ExternalService, + CreateCommentParams, CreateIncidentParams, - UpdateIncidentParams, - JiraPublicConfigurationType, - JiraSecretConfigurationType, + ExternalService, + ExternalServiceCommentResponse, + ExternalServiceCredentials, + ExternalServiceIncidentResponse, Fields, - CreateCommentParams, + FieldSchema, + GetCommonFieldsResponse, Incident, + JiraPublicConfigurationType, + JiraSecretConfigurationType, ResponseError, - ExternalServiceCommentResponse, - ExternalServiceIncidentResponse, + UpdateIncidentParams, } from './types'; import * as i18n from './translations'; @@ -127,14 +129,21 @@ export const createExternalService = ( issueTypes.map((type) => ({ id: type.id, name: type.name })); const normalizeFields = (fields: { - [key: string]: { allowedValues?: Array<{}>; defaultValue?: {} }; + [key: string]: { + allowedValues?: Array<{}>; + defaultValue?: {}; + required: boolean; + schema: FieldSchema; + }; }) => Object.keys(fields ?? {}).reduce((fieldsAcc, fieldKey) => { return { ...fieldsAcc, [fieldKey]: { + required: fields[fieldKey]?.required, allowedValues: fields[fieldKey]?.allowedValues ?? [], defaultValue: fields[fieldKey]?.defaultValue ?? {}, + schema: fields[fieldKey]?.schema, }, }; }, {}); @@ -326,7 +335,6 @@ export const createExternalService = ( const getIssueTypes = async () => { const capabilitiesResponse = await getCapabilities(); const supportsNewAPI = hasSupportForNewAPI(capabilitiesResponse); - try { if (!supportsNewAPI) { const res = await request({ @@ -366,7 +374,6 @@ export const createExternalService = ( const getFieldsByIssueType = async (issueTypeId: string) => { const capabilitiesResponse = await getCapabilities(); const supportsNewAPI = hasSupportForNewAPI(capabilitiesResponse); - try { if (!supportsNewAPI) { const res = await request({ @@ -378,6 +385,7 @@ export const createExternalService = ( }); const fields = res.data.projects[0]?.issuetypes[0]?.fields || {}; + return normalizeFields(fields); } else { const res = await request({ @@ -409,6 +417,30 @@ export const createExternalService = ( } }; + const getFields = async () => { + try { + const issueTypes = await getIssueTypes(); + const fieldsPerIssueType = await Promise.all( + issueTypes.map((issueType) => getFieldsByIssueType(issueType.id)) + ); + return fieldsPerIssueType.reduce((acc: GetCommonFieldsResponse, fieldTypesByIssue) => { + const currentListOfFields = Object.keys(acc); + return currentListOfFields.length === 0 + ? fieldTypesByIssue + : currentListOfFields.reduce( + (add: GetCommonFieldsResponse, field) => + Object.keys(fieldTypesByIssue).includes(field) + ? { ...add, [field]: acc[field] } + : add, + {} + ); + }, {}); + } catch (error) { + // errors that happen here would be thrown in the contained async calls + throw error; + } + }; + const getIssues = async (title: string) => { const query = `${searchUrl}?jql=${encodeURIComponent( `project="${projectKey}" and summary ~"${title}"` @@ -461,6 +493,7 @@ export const createExternalService = ( }; return { + getFields, getIncident, createIncident, updateIncident, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts index 7d650a22fba1bb..e142637010a986 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts @@ -79,11 +79,34 @@ export interface CreateCommentParams { comment: Comment; } +export interface FieldsSchema { + type: string; + [key: string]: string; +} + +export interface ExternalServiceFields { + clauseNames: string[]; + custom: boolean; + id: string; + key: string; + name: string; + navigatable: boolean; + orderable: boolean; + schema: FieldsSchema; + searchable: boolean; +} + export type GetIssueTypesResponse = Array<{ id: string; name: string }>; + +export interface FieldSchema { + type: string; + items?: string; +} export type GetFieldsByIssueTypeResponse = Record< string, - { allowedValues: Array<{}>; defaultValue: {} } + { allowedValues: Array<{}>; defaultValue: {}; required: boolean; schema: FieldSchema } >; +export type GetCommonFieldsResponse = GetFieldsByIssueTypeResponse; export type GetIssuesResponse = Array<{ id: string; key: string; title: string }>; export interface GetIssueResponse { @@ -93,15 +116,16 @@ export interface GetIssueResponse { } export interface ExternalService { - getIncident: (id: string) => Promise; - createIncident: (params: CreateIncidentParams) => Promise; - updateIncident: (params: UpdateIncidentParams) => Promise; createComment: (params: CreateCommentParams) => Promise; + createIncident: (params: CreateIncidentParams) => Promise; + getFields: () => Promise; getCapabilities: () => Promise; - getIssueTypes: () => Promise; getFieldsByIssueType: (issueTypeId: string) => Promise; - getIssues: (title: string) => Promise; + getIncident: (id: string) => Promise; getIssue: (id: string) => Promise; + getIssues: (title: string) => Promise; + getIssueTypes: () => Promise; + updateIncident: (params: UpdateIncidentParams) => Promise; } export interface PushToServiceApiParams extends ExecutorSubActionPushParams { @@ -157,6 +181,11 @@ export interface GetIssueTypesHandlerArgs { params: ExecutorSubActionGetIssueTypesParams; } +export interface GetCommonFieldsHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionGetIssueTypesParams; +} + export interface GetFieldsByIssueTypeHandlerArgs { externalService: ExternalService; params: ExecutorSubActionGetFieldsByIssueTypeParams; @@ -177,15 +206,16 @@ export interface GetIssueHandlerArgs { } export interface ExternalServiceApi { - handshake: (args: HandshakeApiHandlerArgs) => Promise; - pushToService: (args: PushToServiceApiHandlerArgs) => Promise; + getFields: (args: GetCommonFieldsHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; + handshake: (args: HandshakeApiHandlerArgs) => Promise; issueTypes: (args: GetIssueTypesHandlerArgs) => Promise; + pushToService: (args: PushToServiceApiHandlerArgs) => Promise; fieldsByIssueType: ( args: GetFieldsByIssueTypeHandlerArgs ) => Promise; - issues: (args: GetIssuesHandlerArgs) => Promise; issue: (args: GetIssueHandlerArgs) => Promise; + issues: (args: GetIssuesHandlerArgs) => Promise; } export type JiraExecutorResultData = diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts index 46d9c114297a9c..29f2594d2b6f8a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts @@ -15,6 +15,7 @@ import { GetSeverityHandlerArgs, PushToServiceApiParams, PushToServiceResponse, + GetCommonFieldsHandlerArgs, } from './types'; // TODO: to remove, need to support Case @@ -32,6 +33,10 @@ const getIncidentHandler = async ({ params, }: GetIncidentApiHandlerArgs) => {}; +const getFieldsHandler = async ({ externalService }: GetCommonFieldsHandlerArgs) => { + const res = await externalService.getFields(); + return res; +}; const getIncidentTypesHandler = async ({ externalService }: GetIncidentTypesHandlerArgs) => { const res = await externalService.getIncidentTypes(); return res; @@ -136,9 +141,10 @@ const pushToServiceHandler = async ({ }; export const api: ExternalServiceApi = { - handshake: handshakeHandler, - pushToService: pushToServiceHandler, + getFields: getFieldsHandler, getIncident: getIncidentHandler, + handshake: handshakeHandler, incidentTypes: getIncidentTypesHandler, + pushToService: pushToServiceHandler, severity: getSeverityHandler, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts index 53285a2a350aff..6203dda4120f56 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts @@ -25,6 +25,7 @@ import { ResilientExecutorResultData, ExecutorSubActionGetIncidentTypesParams, ExecutorSubActionGetSeverityParams, + ExecutorSubActionCommonFieldsParams, } from './types'; import * as i18n from './translations'; import { Logger } from '../../../../../../src/core/server'; @@ -37,7 +38,7 @@ interface GetActionTypeParams { configurationUtilities: ActionsConfigurationUtilities; } -const supportedSubActions: string[] = ['pushToService', 'incidentTypes', 'severity']; +const supportedSubActions: string[] = ['getFields', 'pushToService', 'incidentTypes', 'severity']; // action type definition export function getActionType( @@ -122,6 +123,14 @@ async function executor( logger.debug(`response push to service for incident id: ${data.id}`); } + if (subAction === 'getFields') { + const getFieldsParams = subActionParams as ExecutorSubActionCommonFieldsParams; + data = await api.getFields({ + externalService, + params: getFieldsParams, + }); + } + if (subAction === 'incidentTypes') { const incidentTypesParams = subActionParams as ExecutorSubActionGetIncidentTypesParams; data = await api.incidentTypes({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts index 2e841728159a3b..2b2a22a66b7098 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts @@ -8,8 +8,275 @@ import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } import { MapRecord } from '../case/types'; +export const resilientFields = [ + { + id: 17, + name: 'name', + text: 'Name', + prefix: null, + type_id: 0, + tooltip: 'A unique name to identify this particular incident.', + input_type: 'text', + required: 'always', + hide_notification: false, + chosen: false, + default_chosen_by_server: false, + blank_option: false, + internal: true, + uuid: 'ad6ed4f2-8d87-4ba2-81fa-03568a9326cc', + operations: [ + 'equals', + 'not_equals', + 'contains', + 'not_contains', + 'changed', + 'changed_to', + 'not_changed_to', + 'has_a_value', + 'not_has_a_value', + ], + operation_perms: { + changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + changed: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + }, + values: [], + perms: { + delete: false, + modify_name: false, + modify_values: false, + modify_blank: false, + modify_required: false, + modify_operations: false, + modify_chosen: false, + modify_default: false, + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + show_in_scripts: true, + modify_type: ['text'], + sort: true, + }, + read_only: false, + changeable: true, + rich_text: false, + templates: [], + deprecated: false, + tags: [], + calculated: false, + is_tracked: false, + allow_default_value: false, + }, + { + id: 15, + name: 'description', + text: 'Description', + prefix: null, + type_id: 0, + tooltip: 'A free form text description of the incident.', + input_type: 'textarea', + hide_notification: false, + chosen: false, + default_chosen_by_server: false, + blank_option: false, + internal: true, + uuid: '420d70b1-98f9-4681-a20b-84f36a9e5e48', + operations: [ + 'equals', + 'not_equals', + 'contains', + 'not_contains', + 'changed', + 'changed_to', + 'not_changed_to', + 'has_a_value', + 'not_has_a_value', + ], + operation_perms: { + changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + changed: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + }, + values: [], + perms: { + delete: false, + modify_name: false, + modify_values: false, + modify_blank: false, + modify_required: false, + modify_operations: false, + modify_chosen: false, + modify_default: false, + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + show_in_scripts: true, + modify_type: ['textarea'], + sort: true, + }, + read_only: false, + changeable: true, + rich_text: true, + templates: [], + deprecated: false, + tags: [], + calculated: false, + is_tracked: false, + allow_default_value: false, + }, + { + id: 65, + name: 'create_date', + text: 'Date Created', + prefix: null, + type_id: 0, + tooltip: 'The date the incident was created. This field is read-only.', + input_type: 'datetimepicker', + hide_notification: false, + chosen: false, + default_chosen_by_server: false, + blank_option: false, + internal: true, + uuid: 'b4faf728-881a-4e8b-bf0b-d39b720392a1', + operations: ['due_within', 'overdue_by', 'has_a_value', 'not_has_a_value'], + operation_perms: { + has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + due_within: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + overdue_by: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + }, + values: [], + perms: { + delete: false, + modify_name: false, + modify_values: false, + modify_blank: false, + modify_required: false, + modify_operations: false, + modify_chosen: false, + modify_default: false, + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + show_in_scripts: true, + modify_type: ['datetimepicker'], + sort: true, + }, + read_only: true, + changeable: false, + rich_text: false, + templates: [], + deprecated: false, + tags: [], + calculated: false, + is_tracked: false, + allow_default_value: false, + }, +]; + const createMock = (): jest.Mocked => { const service = { + getFields: jest.fn().mockImplementation(() => Promise.resolve(resilientFields)), getIncident: jest.fn().mockImplementation(() => Promise.resolve({ id: '1', diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts index b6e3a9525dfd4f..c7ceba94140fb1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts @@ -53,11 +53,16 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ }); // Reserved for future implementation +export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); export const ExecutorSubActionGetIncidentTypesParamsSchema = schema.object({}); export const ExecutorSubActionGetSeverityParamsSchema = schema.object({}); export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), schema.object({ subAction: schema.literal('getIncident'), subActionParams: ExecutorSubActionGetIncidentParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts index 86ea352625a5b8..ecf246cb8fe3c6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts @@ -11,7 +11,7 @@ import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { incidentTypes, severity } from './mocks'; +import { incidentTypes, resilientFields, severity } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -231,7 +231,7 @@ describe('IBM Resilient service', () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); }); - expect(service.getIncident('1')).rejects.toThrow( + await expect(service.getIncident('1')).rejects.toThrow( 'Unable to get incident with id 1. Error: An error has occurred' ); }); @@ -310,7 +310,7 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.createIncident({ incident: { name: 'title', @@ -418,7 +418,7 @@ describe('IBM Resilient service', () => { test('it should throw an error', async () => { mockIncidentUpdate(true); - expect( + await expect( service.updateIncident({ incidentId: '1', incident: { @@ -502,7 +502,7 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.createComment({ incidentId: '1', comment: { @@ -541,7 +541,7 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect(service.getIncidentTypes()).rejects.toThrow( + await expect(service.getIncidentTypes()).rejects.toThrow( '[Action][IBM Resilient]: Unable to get incident types. Error: An error has occurred.' ); }); @@ -578,9 +578,40 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect(service.getIncidentTypes()).rejects.toThrow( + await expect(service.getIncidentTypes()).rejects.toThrow( '[Action][IBM Resilient]: Unable to get incident types. Error: An error has occurred.' ); }); }); + + describe('getFields', () => { + test('it should call request with correct arguments', async () => { + requestMock.mockImplementation(() => ({ + data: resilientFields, + })); + await service.getFields(); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + url: 'https://resilient.elastic.co/rest/orgs/201/types/incident/fields', + }); + }); + test('it returns common fields correctly', async () => { + requestMock.mockImplementation(() => ({ + data: resilientFields, + })); + const res = await service.getFields(); + expect(res).toEqual(resilientFields); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + throw new Error('An error has occurred'); + }); + await expect(service.getFields()).rejects.toThrow( + 'Unable to get fields. Error: An error has occurred' + ); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts index 4bf1453641e426..a13204f8bb1d83 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts @@ -303,12 +303,27 @@ export const createExternalService = ( } }; + const getFields = async () => { + try { + const res = await request({ + axios: axiosInstance, + url: incidentFieldsUrl, + logger, + proxySettings, + }); + return res.data ?? []; + } catch (error) { + throw new Error(getErrorMessage(i18n.NAME, `Unable to get fields. Error: ${error.message}.`)); + } + }; + return { - getIncident, - createIncident, - updateIncident, createComment, + createIncident, + getFields, + getIncident, getIncidentTypes, getSeverity, + updateIncident, }; }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts index ed622ee473b659..a70420b30a0926 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts @@ -8,14 +8,15 @@ import { TypeOf } from '@kbn/config-schema'; import { - ExternalIncidentServiceConfigurationSchema, - ExternalIncidentServiceSecretConfigurationSchema, ExecutorParamsSchema, - ExecutorSubActionPushParamsSchema, + ExecutorSubActionCommonFieldsParamsSchema, ExecutorSubActionGetIncidentParamsSchema, - ExecutorSubActionHandshakeParamsSchema, ExecutorSubActionGetIncidentTypesParamsSchema, ExecutorSubActionGetSeverityParamsSchema, + ExecutorSubActionHandshakeParamsSchema, + ExecutorSubActionPushParamsSchema, + ExternalIncidentServiceConfigurationSchema, + ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; @@ -31,6 +32,10 @@ export type ResilientSecretConfigurationType = TypeOf< typeof ExternalIncidentServiceSecretConfigurationSchema >; +export type ExecutorSubActionCommonFieldsParams = TypeOf< + typeof ExecutorSubActionCommonFieldsParamsSchema +>; + export type ExecutorParams = TypeOf; export type ExecutorSubActionPushParams = TypeOf; @@ -60,6 +65,14 @@ export interface ExternalServiceCommentResponse { } export type ExternalServiceParams = Record; +export interface ExternalServiceFields { + id: string; + input_type: string; + name: string; + read_only: boolean; + required?: string; +} +export type GetCommonFieldsResponse = ExternalServiceFields[]; export type Incident = Pick< ExecutorSubActionPushParams, @@ -86,12 +99,13 @@ export type GetIncidentTypesResponse = Array<{ id: string; name: string }>; export type GetSeverityResponse = Array<{ id: string; name: string }>; export interface ExternalService { - getIncident: (id: string) => Promise; - createIncident: (params: CreateIncidentParams) => Promise; - updateIncident: (params: UpdateIncidentParams) => Promise; createComment: (params: CreateCommentParams) => Promise; + createIncident: (params: CreateIncidentParams) => Promise; + getFields: () => Promise; + getIncident: (id: string) => Promise; getIncidentTypes: () => Promise; getSeverity: () => Promise; + updateIncident: (params: UpdateIncidentParams) => Promise; } export interface PushToServiceApiParams extends ExecutorSubActionPushParams { @@ -132,6 +146,11 @@ export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs { params: ExecutorSubActionHandshakeParams; } +export interface GetCommonFieldsHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionCommonFieldsParams; +} + export interface GetIncidentTypesHandlerArgs { externalService: ExternalService; params: ExecutorSubActionGetIncidentTypesParams; @@ -147,6 +166,7 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse { } export interface ExternalServiceApi { + getFields: (args: GetCommonFieldsHandlerArgs) => Promise; handshake: (args: HandshakeApiHandlerArgs) => Promise; pushToService: (args: PushToServiceApiHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; @@ -156,6 +176,7 @@ export interface ExternalServiceApi { export type ResilientExecutorResultData = | PushToServiceResponse + | GetCommonFieldsResponse | GetIncidentTypesResponse | GetSeverityResponse; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts index d49c2f265d04ff..4683b661e21da6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts @@ -5,7 +5,7 @@ */ import { Logger } from '../../../../../../src/core/server'; -import { externalServiceMock, mapping, apiParams } from './mocks'; +import { externalServiceMock, mapping, apiParams, serviceNowCommonFields } from './mocks'; import { ExternalService } from './types'; import { api } from './api'; let mockedLogger: jest.Mocked; @@ -15,634 +15,619 @@ describe('api', () => { beforeEach(() => { externalService = externalServiceMock.create(); - jest.clearAllMocks(); }); - afterEach(() => { - jest.clearAllMocks(); - }); + describe('create incident', () => { + test('it creates an incident', async () => { + const params = { ...apiParams, externalId: null }; + const res = await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); - describe('pushToService', () => { - describe('create incident', () => { - test('it creates an incident', async () => { - const params = { ...apiParams, externalId: null }; - const res = await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'INC01', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - ], - }); - }); - - test('it creates an incident without comments', async () => { - const params = { ...apiParams, externalId: null, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'INC01', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - }); - }); - - test('it calls createIncident correctly', async () => { - const params = { ...apiParams, externalId: null, comments: [] }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: { username: 'elastic', password: 'elastic' }, - logger: mockedLogger, - }); - - expect(externalService.createIncident).toHaveBeenCalledWith({ - incident: { - severity: '1', - urgency: '2', - impact: '3', - caller_id: 'elastic', - description: - 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - expect(externalService.updateIncident).not.toHaveBeenCalled(); - }); - - test('it calls updateIncident correctly when creating an incident and having comments', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledTimes(2); - expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + expect(res).toEqual({ + id: 'incident-1', + title: 'INC01', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + comments: [ + { + commentId: 'case-comment-1', + pushedDate: '2020-03-10T12:24:20.000Z', }, - incidentId: 'incident-1', - }); - - expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - comments: 'Another comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + { + commentId: 'case-comment-2', + pushedDate: '2020-03-10T12:24:20.000Z', }, - incidentId: 'incident-1', - }); + ], }); }); - describe('update incident', () => { - test('it updates an incident', async () => { - const res = await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-2', - title: 'INC02', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - ], - }); - }); - - test('it updates an incident without comments', async () => { - const params = { ...apiParams, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-2', - title: 'INC02', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - }); - }); - - test('it calls updateIncident correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - expect(externalService.createIncident).not.toHaveBeenCalled(); - }); - - test('it calls updateIncident to create a comments correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledTimes(3); - expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - incidentId: 'incident-3', - }); - - expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - incidentId: 'incident-2', - }); + test('it creates an incident without comments', async () => { + const params = { ...apiParams, externalId: null, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-1', + title: 'INC01', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', }); }); - describe('mapping variations', () => { - test('overwrite & append', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('nothing & append', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('append & append', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('nothing & nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - }, - }); - }); - - test('overwrite & nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('overwrite & overwrite', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('nothing & overwrite', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('append & overwrite', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + test('it calls createIncident correctly', async () => { + const params = { ...apiParams, externalId: null, comments: [] }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: { username: 'elastic', password: 'elastic' }, + logger: mockedLogger, + }); + + expect(externalService.createIncident).toHaveBeenCalledWith({ + incident: { + severity: '1', + urgency: '2', + impact: '3', + caller_id: 'elastic', + description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + expect(externalService.updateIncident).not.toHaveBeenCalled(); + }); + + test('it calls updateIncident correctly when creating an incident and having comments', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(2); + expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-1', + }); + + expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + comments: 'Another comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-1', + }); + }); + }); + + describe('update incident', () => { + test('it updates an incident', async () => { + const res = await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + comments: [ + { + commentId: 'case-comment-1', + pushedDate: '2020-03-10T12:24:20.000Z', }, - }); - }); - - test('append & nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + { + commentId: 'case-comment-2', + pushedDate: '2020-03-10T12:24:20.000Z', }, - }); - }); - - test('comment nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'nothing', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledTimes(1); + ], + }); + }); + + test('it updates an incident without comments', async () => { + const params = { ...apiParams, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + }); + }); + + test('it calls updateIncident correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + expect(externalService.createIncident).not.toHaveBeenCalled(); + }); + + test('it calls updateIncident to create a comments correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(3); + expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-3', + }); + + expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-2', + }); + }); + }); + + describe('mapping variations', () => { + test('overwrite & append', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: + 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('nothing & append', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: + 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('append & append', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: + 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: + 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('nothing & nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + }, + }); + }); + + test('overwrite & nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('overwrite & overwrite', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('nothing & overwrite', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('append & overwrite', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: + 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('append & nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: + 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('comment nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'nothing', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(1); + }); + }); + + describe('getFields', () => { + test('it returns the fields correctly', async () => { + const res = await api.getFields({ + externalService, + params: {}, }); + expect(res).toEqual(serviceNowCommonFields); }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts index 6d12a3c92dac70..fbd8fdd635d703 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts @@ -12,6 +12,8 @@ import { PushToServiceApiParams, PushToServiceResponse, Incident, + GetCommonFieldsHandlerArgs, + GetCommonFieldsResponse, } from './types'; // TODO: to remove, need to support Case @@ -127,8 +129,16 @@ const pushToServiceHandler = async ({ return res; }; +const getFieldsHandler = async ({ + externalService, +}: GetCommonFieldsHandlerArgs): Promise => { + const res = await externalService.getFields(); + return res; +}; + export const api: ExternalServiceApi = { + getFields: getFieldsHandler, + getIncident: getIncidentHandler, handshake: handshakeHandler, pushToService: pushToServiceHandler, - getIncident: getIncidentHandler, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index 41a577918b18ee..d1182b0d3b2fae 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -25,6 +25,8 @@ import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType, PushToServiceResponse, + ExecutorSubActionCommonFieldsParams, + ServiceNowExecutorResultData, } from './types'; // TODO: to remove, need to support Case @@ -63,7 +65,7 @@ export function getActionType( } // action executor - +const supportedSubActions: string[] = ['getFields', 'pushToService']; async function executor( { logger }: { logger: Logger }, execOptions: ActionTypeExecutorOptions< @@ -71,10 +73,10 @@ async function executor( ServiceNowSecretConfigurationType, ExecutorParams > -): Promise> { +): Promise> { const { actionId, config, params, secrets } = execOptions; const { subAction, subActionParams } = params; - let data: PushToServiceResponse | null = null; + let data: ServiceNowExecutorResultData | null = null; const externalService = createExternalService( { @@ -91,7 +93,7 @@ async function executor( throw new Error(errorMessage); } - if (subAction !== 'pushToService') { + if (!supportedSubActions.includes(subAction)) { const errorMessage = `[Action][ExternalService] subAction ${subAction} not implemented.`; logger.error(errorMessage); throw new Error(errorMessage); @@ -117,5 +119,13 @@ async function executor( logger.debug(`response push to service for incident id: ${data.id}`); } + if (subAction === 'getFields') { + const getFieldsParams = subActionParams as ExecutorSubActionCommonFieldsParams; + data = await api.getFields({ + externalService, + params: getFieldsParams, + }); + } + return { status: 'ok', data: data ?? {}, actionId }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts index 7c2b1bd9d73c1e..2351be36a50c4e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts @@ -7,8 +7,36 @@ import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } from './types'; import { MapRecord } from '../case/types'; +export const serviceNowCommonFields = [ + { + column_label: 'Close notes', + max_length: '4000', + element: 'close_notes', + }, + { + column_label: 'Description', + max_length: '4000', + element: 'description', + }, + { + column_label: 'Short description', + max_length: '160', + element: 'short_description', + }, + { + column_label: 'Created by', + max_length: '40', + element: 'sys_created_by', + }, + { + column_label: 'Updated by', + max_length: '40', + element: 'sys_updated_by', + }, +]; const createMock = (): jest.Mocked => { const service = { + getFields: jest.fn().mockImplementation(() => Promise.resolve(serviceNowCommonFields)), getIncident: jest.fn().mockImplementation(() => Promise.resolve({ short_description: 'title from servicenow', diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts index 0dd70ea36636e7..77c48aab1f3094 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts @@ -28,6 +28,7 @@ export const ExternalIncidentServiceSecretConfigurationSchema = schema.object( ); export const ExecutorSubActionSchema = schema.oneOf([ + schema.literal('getFields'), schema.literal('getIncident'), schema.literal('pushToService'), schema.literal('handshake'), @@ -53,8 +54,13 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ // Reserved for future implementation export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); +export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), schema.object({ subAction: schema.literal('getIncident'), subActionParams: ExecutorSubActionGetIncidentParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts index 2adcdf561ce175..8ec80be1e2b094 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts @@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { serviceNowCommonFields } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); @@ -108,7 +109,7 @@ describe('ServiceNow service', () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); }); - expect(service.getIncident('1')).rejects.toThrow( + await expect(service.getIncident('1')).rejects.toThrow( 'Unable to get incident with id 1. Error: An error has occurred' ); }); @@ -155,7 +156,7 @@ describe('ServiceNow service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.createIncident({ incident: { short_description: 'title', description: 'desc' }, }) @@ -207,7 +208,7 @@ describe('ServiceNow service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.updateIncident({ incidentId: '1', incident: { short_description: 'title', description: 'desc' }, @@ -234,4 +235,36 @@ describe('ServiceNow service', () => { }); }); }); + + describe('getFields', () => { + test('it should call request with correct arguments', async () => { + requestMock.mockImplementation(() => ({ + data: { result: serviceNowCommonFields }, + })); + await service.getFields(); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + url: + 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^internal_type=string&active=true&read_only=false&sysparm_fields=max_length,element,column_label', + }); + }); + test('it returns common fields correctly', async () => { + requestMock.mockImplementation(() => ({ + data: { result: serviceNowCommonFields }, + })); + const res = await service.getFields(); + expect(res).toEqual(serviceNowCommonFields); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + throw new Error('An error has occurred'); + }); + await expect(service.getFields()).rejects.toThrow( + 'Unable to get common fields. Error: An error has occurred' + ); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts index 9b1da4b4007c66..57f7176e2353c8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts @@ -16,6 +16,7 @@ import { ProxySettings } from '../../types'; const API_VERSION = 'v2'; const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`; +const SYS_DICTIONARY = `api/now/${API_VERSION}/table/sys_dictionary`; // Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`; @@ -33,6 +34,7 @@ export const createExternalService = ( } const incidentUrl = `${url}/${INCIDENT_URL}`; + const fieldsUrl = `${url}/${SYS_DICTIONARY}?sysparm_query=name=task^internal_type=string&active=true&read_only=false&sysparm_fields=max_length,element,column_label`; const axiosInstance = axios.create({ auth: { username, password }, }); @@ -126,10 +128,28 @@ export const createExternalService = ( } }; + const getFields = async () => { + try { + const res = await request({ + axios: axiosInstance, + url: fieldsUrl, + logger, + proxySettings, + }); + + return res.data.result.length > 0 ? res.data.result : []; + } catch (error) { + throw new Error( + getErrorMessage(i18n.NAME, `Unable to get common fields. Error: ${error.message}`) + ); + } + }; + return { - getIncident, createIncident, - updateIncident, findIncidents, + getFields, + getIncident, + updateIncident, }; }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index a6a0ac946fe96a..0ee03f883ec05b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -8,12 +8,13 @@ import { TypeOf } from '@kbn/config-schema'; import { - ExternalIncidentServiceConfigurationSchema, - ExternalIncidentServiceSecretConfigurationSchema, ExecutorParamsSchema, - ExecutorSubActionPushParamsSchema, + ExecutorSubActionCommonFieldsParamsSchema, ExecutorSubActionGetIncidentParamsSchema, ExecutorSubActionHandshakeParamsSchema, + ExecutorSubActionPushParamsSchema, + ExternalIncidentServiceConfigurationSchema, + ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; import { ExternalServiceCommentResponse } from '../case/types'; @@ -27,6 +28,12 @@ export type ServiceNowSecretConfigurationType = TypeOf< typeof ExternalIncidentServiceSecretConfigurationSchema >; +export type ExecutorSubActionCommonFieldsParams = TypeOf< + typeof ExecutorSubActionCommonFieldsParamsSchema +>; + +export type ServiceNowExecutorResultData = PushToServiceResponse | GetCommonFieldsResponse; + export interface CreateCommentRequest { [key: string]: string; } @@ -59,6 +66,7 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse { export type ExternalServiceParams = Record; export interface ExternalService { + getFields: () => Promise; getIncident: (id: string) => Promise; createIncident: (params: ExternalServiceParams) => Promise; updateIncident: (params: ExternalServiceParams) => Promise; @@ -102,8 +110,24 @@ export interface GetIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs { params: ExecutorSubActionHandshakeParams; } +export interface ExternalServiceFields { + column_label: string; + name: string; + internal_type: { + link: string; + value: string; + }; + max_length: string; + element: string; +} +export type GetCommonFieldsResponse = ExternalServiceFields[]; +export interface GetCommonFieldsHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionCommonFieldsParams; +} export interface ExternalServiceApi { + getFields: (args: GetCommonFieldsHandlerArgs) => Promise; handshake: (args: HandshakeApiHandlerArgs) => Promise; pushToService: (args: PushToServiceApiHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts index 39f64dd037945a..4e9293b74c99e7 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts @@ -333,7 +333,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -351,7 +351,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -374,7 +374,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -402,7 +402,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -430,7 +430,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts index 5d54ea99889c17..34c1c757ab119d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts @@ -334,7 +334,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -352,7 +352,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -375,7 +375,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -403,7 +403,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -431,7 +431,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts index 0684707c738245..5b4db53a59a413 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -328,7 +328,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]', }); }); }); @@ -346,7 +346,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]', }); }); }); @@ -369,7 +369,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]', }); }); }); @@ -397,7 +397,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments.0.commentId]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments.0.commentId]: expected value of type [string] but got [undefined]', }); }); }); @@ -425,7 +425,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments.0.comment]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments.0.comment]: expected value of type [string] but got [undefined]', }); }); }); From fdb9d76fbda8e1dd0cc9fd43c903c71702d77ad7 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Mon, 9 Nov 2020 10:31:21 -0700 Subject: [PATCH 80/81] Uses asCurrentUser in getClusterUuid (#82908) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../usage_collection/server/routes/stats/stats.ts | 14 +++++++++----- .../api_integration/apis/kibana/stats/stats.js | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/plugins/usage_collection/server/routes/stats/stats.ts b/src/plugins/usage_collection/server/routes/stats/stats.ts index ef64d15fabc2d0..d38250067053c1 100644 --- a/src/plugins/usage_collection/server/routes/stats/stats.ts +++ b/src/plugins/usage_collection/server/routes/stats/stats.ts @@ -73,8 +73,9 @@ export function registerStatsRoute({ return collectorSet.toObject(usage); }; - const getClusterUuid = async (callCluster: LegacyAPICaller): Promise => { - const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid' }); + const getClusterUuid = async (asCurrentUser: ElasticsearchClient): Promise => { + const { body } = await asCurrentUser.info({ filter_path: 'cluster_uuid' }); + const { cluster_uuid: uuid } = body; return uuid; }; @@ -103,7 +104,7 @@ export function registerStatsRoute({ let extended; if (isExtended) { const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; - const esClient = context.core.elasticsearch.client.asCurrentUser; + const { asCurrentUser } = context.core.elasticsearch.client; const savedObjectsClient = context.core.savedObjects.client; if (shouldGetUsage) { @@ -114,9 +115,12 @@ export function registerStatsRoute({ } const usagePromise = shouldGetUsage - ? getUsage(callCluster, esClient, savedObjectsClient) + ? getUsage(callCluster, asCurrentUser, savedObjectsClient) : Promise.resolve({}); - const [usage, clusterUuid] = await Promise.all([usagePromise, getClusterUuid(callCluster)]); + const [usage, clusterUuid] = await Promise.all([ + usagePromise, + getClusterUuid(asCurrentUser), + ]); let modifiedUsage = usage; if (isLegacy) { diff --git a/x-pack/test/api_integration/apis/kibana/stats/stats.js b/x-pack/test/api_integration/apis/kibana/stats/stats.js index f0a41f1f008ba3..ae4ddad66863b5 100644 --- a/x-pack/test/api_integration/apis/kibana/stats/stats.js +++ b/x-pack/test/api_integration/apis/kibana/stats/stats.js @@ -30,7 +30,7 @@ export default function ({ getService }) { }); it('should return 401 for extended', async () => { - await supertestNoAuth.get('/api/stats?extended').expect(401); + await supertestNoAuth.get('/api/stats?extended').auth(null, null).expect(401); }); }); From 83e51f56885f9629d6849ce5bad2c2569b6eb91f Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Mon, 9 Nov 2020 11:46:36 -0600 Subject: [PATCH 81/81] add alternate path for x-pack/Cloud test for Lens (#82634) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/management/_scripted_fields.js | 152 +++++++++++------- 1 file changed, 93 insertions(+), 59 deletions(-) diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 6da9ebed0538a8..5ca01f239e762f 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -198,35 +198,44 @@ export default function ({ getService, getPageObjects }) { }); it('should visualize scripted field in vertical bar chart', async function () { - const expectedChartValues = [ - ['14', '31'], - ['10', '29'], - ['7', '24'], - ['11', '24'], - ['12', '23'], - ['20', '23'], - ['19', '21'], - ['6', '20'], - ['17', '20'], - ['30', '20'], - ['13', '19'], - ['18', '18'], - ['16', '17'], - ['5', '16'], - ['8', '16'], - ['15', '14'], - ['3', '13'], - ['2', '12'], - ['9', '10'], - ['4', '9'], - ]; await filterBar.removeAllFilters(); await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.setTablePageSize(50); - await inspector.expectTableData(expectedChartValues); + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + const expectedChartValues = [ + ['14', '31'], + ['10', '29'], + ['7', '24'], + ['11', '24'], + ['12', '23'], + ['20', '23'], + ['19', '21'], + ['6', '20'], + ['17', '20'], + ['30', '20'], + ['13', '19'], + ['18', '18'], + ['16', '17'], + ['5', '16'], + ['8', '16'], + ['15', '14'], + ['3', '13'], + ['2', '12'], + ['9', '10'], + ['4', '9'], + ]; + + await inspector.open(); + await inspector.setTablePageSize(50); + await inspector.expectTableData(expectedChartValues); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'Average of ram_Pain1' + ); + } }); }); @@ -309,11 +318,19 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.expectTableData([ - ['good', '359'], - ['bad', '27'], - ]); + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + await inspector.open(); + await inspector.expectTableData([ + ['good', '359'], + ['bad', '27'], + ]); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'Top values of painString' + ); + } }); }); @@ -397,11 +414,19 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.expectTableData([ - ['true', '359'], - ['false', '27'], - ]); + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + await inspector.open(); + await inspector.expectTableData([ + ['true', '359'], + ['false', '27'], + ]); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'Top values of painBool' + ); + } }); }); @@ -488,30 +513,39 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.setTablePageSize(50); - await inspector.expectTableData([ - ['2015-09-17 20:00', '1'], - ['2015-09-17 21:00', '1'], - ['2015-09-17 23:00', '1'], - ['2015-09-18 00:00', '1'], - ['2015-09-18 03:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 07:00', '1'], - ['2015-09-18 07:00', '1'], - ['2015-09-18 07:00', '1'], - ]); + + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + await inspector.open(); + await inspector.setTablePageSize(50); + await inspector.expectTableData([ + ['2015-09-17 20:00', '1'], + ['2015-09-17 21:00', '1'], + ['2015-09-17 23:00', '1'], + ['2015-09-18 00:00', '1'], + ['2015-09-18 03:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 07:00', '1'], + ['2015-09-18 07:00', '1'], + ['2015-09-18 07:00', '1'], + ]); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'painDate' + ); + } }); }); });