From 2399780d99a812a3d9a0c1957ea47582e0107072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Mon, 6 Jul 2020 17:37:28 +0200 Subject: [PATCH 01/21] [Logs + Metrics UI] Add index names for the new indexing strategy (#70245) This add support for the new index name patterns `logs-*` and `metrics-*` of the new indexing strategy to the Logs and Metrics UI source configurations in the form of a migration and changed defaults. --- .../add_log_column_popover.tsx | 161 --------- .../fields_configuration_panel.tsx | 328 +++++++----------- .../indices_configuration_panel.tsx | 136 +++----- .../log_columns_configuration_panel.tsx | 279 --------------- .../source_configuration_settings.tsx | 26 -- .../settings/indices_configuration_panel.tsx | 2 +- .../infra/public/pages/metrics/settings.tsx | 1 - .../infra/server/lib/sources/defaults.ts | 4 +- ..._new_indexing_strategy_index_names.test.ts | 131 +++++++ ...0_add_new_indexing_strategy_index_names.ts | 36 ++ .../server/lib/sources/saved_object_type.ts | 5 +- .../apis/metrics_ui/log_sources.ts | 6 +- .../apis/metrics_ui/sources.ts | 10 +- 13 files changed, 358 insertions(+), 767 deletions(-) delete mode 100644 x-pack/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx delete mode 100644 x-pack/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx create mode 100644 x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.test.ts create mode 100644 x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.ts diff --git a/x-pack/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx b/x-pack/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx deleted file mode 100644 index 9f55126a1440..000000000000 --- a/x-pack/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx +++ /dev/null @@ -1,161 +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 { EuiBadge, EuiButton, EuiPopover, EuiPopoverTitle, EuiSelectable } from '@elastic/eui'; -import { EuiSelectableOption } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useCallback, useMemo } from 'react'; -import { v4 as uuidv4 } from 'uuid'; - -import { LogColumnConfiguration } from '../../utils/source_configuration'; -import { useVisibilityState } from '../../utils/use_visibility_state'; -import { euiStyled } from '../../../../observability/public'; - -interface SelectableColumnOption { - optionProps: EuiSelectableOption; - columnConfiguration: LogColumnConfiguration; -} - -export const AddLogColumnButtonAndPopover: React.FunctionComponent<{ - addLogColumn: (logColumnConfiguration: LogColumnConfiguration) => void; - availableFields: string[]; - isDisabled?: boolean; -}> = ({ addLogColumn, availableFields, isDisabled }) => { - const { isVisible: isOpen, show: openPopover, hide: closePopover } = useVisibilityState(false); - - const availableColumnOptions = useMemo( - () => [ - { - optionProps: { - append: , - 'data-test-subj': 'addTimestampLogColumn', - // this key works around EuiSelectable using a lowercased label as - // key, which leads to conflicts with field names - key: 'timestamp', - label: 'Timestamp', - }, - columnConfiguration: { - timestampColumn: { - id: uuidv4(), - }, - }, - }, - { - optionProps: { - 'data-test-subj': 'addMessageLogColumn', - append: , - // this key works around EuiSelectable using a lowercased label as - // key, which leads to conflicts with field names - key: 'message', - label: 'Message', - }, - columnConfiguration: { - messageColumn: { - id: uuidv4(), - }, - }, - }, - ...availableFields.map((field) => ({ - optionProps: { - 'data-test-subj': `addFieldLogColumn addFieldLogColumn:${field}`, - // this key works around EuiSelectable using a lowercased label as - // key, which leads to conflicts with fields that only differ in the - // case (e.g. the metricbeat mongodb module) - key: `field-${field}`, - label: field, - }, - columnConfiguration: { - fieldColumn: { - id: uuidv4(), - field, - }, - }, - })), - ], - [availableFields] - ); - - const availableOptions = useMemo( - () => availableColumnOptions.map((availableColumnOption) => availableColumnOption.optionProps), - [availableColumnOptions] - ); - - const handleColumnSelection = useCallback( - (selectedOptions: EuiSelectableOption[]) => { - closePopover(); - - const selectedOptionIndex = selectedOptions.findIndex( - (selectedOption) => selectedOption.checked === 'on' - ); - const selectedOption = availableColumnOptions[selectedOptionIndex]; - - addLogColumn(selectedOption.columnConfiguration); - }, - [addLogColumn, availableColumnOptions, closePopover] - ); - - return ( - - - - } - closePopover={closePopover} - id="addLogColumn" - isOpen={isOpen} - ownFocus - panelPaddingSize="none" - > - - {(list, search) => ( - - {search} - {list} - - )} - - - ); -}; - -const searchProps = { - 'data-test-subj': 'fieldSearchInput', -}; - -const selectableListProps = { - showIcons: false, -}; - -const SystemColumnBadge: React.FunctionComponent = () => ( - - - -); - -const SelectableContent = euiStyled.div` - width: 400px; -`; diff --git a/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 369f07be67bf..5ad05deafd69 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -27,9 +27,7 @@ interface FieldsConfigurationPanelProps { isLoading: boolean; readOnly: boolean; podFieldProps: InputFieldProps; - tiebreakerFieldProps: InputFieldProps; timestampFieldProps: InputFieldProps; - displaySettings: 'metrics' | 'logs'; } export const FieldsConfigurationPanel = ({ @@ -38,15 +36,12 @@ export const FieldsConfigurationPanel = ({ isLoading, readOnly, podFieldProps, - tiebreakerFieldProps, timestampFieldProps, - displaySettings, }: FieldsConfigurationPanelProps) => { const isHostValueDefault = hostFieldProps.value === 'host.name'; const isContainerValueDefault = containerFieldProps.value === 'container.id'; const isPodValueDefault = podFieldProps.value === 'kubernetes.pod.uid'; const isTimestampValueDefault = timestampFieldProps.value === '@timestamp'; - const isTiebreakerValueDefault = tiebreakerFieldProps.value === '_doc'; return ( @@ -139,194 +134,141 @@ export const FieldsConfigurationPanel = ({ /> - {displaySettings === 'logs' && ( - <> - - - - } - description={ - - } - > - _doc, - }} - /> - } - isInvalid={tiebreakerFieldProps.isInvalid} - label={ - - } - > - - - - - )} - {displaySettings === 'metrics' && ( - <> - - - - } - description={ - - } - > - container.id, - }} - /> - } - isInvalid={containerFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - host.name, - }} - /> - } - isInvalid={hostFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - kubernetes.pod.uid, - }} - /> - } - isInvalid={podFieldProps.isInvalid} - label={ - - } - > - - - - - )} + + + + } + description={ + + } + > + container.id, + }} + /> + } + isInvalid={containerFieldProps.isInvalid} + label={ + + } + > + + + + + + + } + description={ + + } + > + host.name, + }} + /> + } + isInvalid={hostFieldProps.isInvalid} + label={ + + } + > + + + + + + + } + description={ + + } + > + kubernetes.pod.uid, + }} + /> + } + isInvalid={podFieldProps.isInvalid} + label={ + + } + > + + + ); }; diff --git a/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx index 1d634b781bd3..e9817331ace9 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx @@ -21,17 +21,13 @@ import { InputFieldProps } from './input_fields'; interface IndicesConfigurationPanelProps { isLoading: boolean; readOnly: boolean; - logAliasFieldProps: InputFieldProps; metricAliasFieldProps: InputFieldProps; - displaySettings: 'metrics' | 'logs'; } export const IndicesConfigurationPanel = ({ isLoading, readOnly, - logAliasFieldProps, metricAliasFieldProps, - displaySettings, }: IndicesConfigurationPanelProps) => ( @@ -43,101 +39,51 @@ export const IndicesConfigurationPanel = ({ - {displaySettings === 'metrics' && ( - - - - } - description={ + + + } + description={ + + } + > + metrics-*,metricbeat-*, + }} + /> + } + isInvalid={metricAliasFieldProps.isInvalid} + label={ + } > - metricbeat-*, - }} - /> - } - isInvalid={metricAliasFieldProps.isInvalid} - label={ - - } - > - - - - )} - {displaySettings === 'logs' && ( - - - - } - description={ - - } - > - filebeat-*, - }} - /> - } - isInvalid={logAliasFieldProps.isInvalid} - label={ - - } - > - - - - )} + disabled={isLoading} + readOnly={readOnly} + isLoading={isLoading} + {...metricAliasFieldProps} + /> + + ); diff --git a/x-pack/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx deleted file mode 100644 index 46ab1e65c29d..000000000000 --- a/x-pack/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx +++ /dev/null @@ -1,279 +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 { - EuiButtonIcon, - EuiEmptyPrompt, - EuiForm, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiDragDropContext, - EuiDraggable, - EuiDroppable, - EuiIcon, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useCallback } from 'react'; -import { DragHandleProps, DropResult } from '../../../../observability/public'; - -import { AddLogColumnButtonAndPopover } from './add_log_column_popover'; -import { - FieldLogColumnConfigurationProps, - LogColumnConfigurationProps, -} from './log_columns_configuration_form_state'; -import { LogColumnConfiguration } from '../../utils/source_configuration'; - -interface LogColumnsConfigurationPanelProps { - availableFields: string[]; - isLoading: boolean; - logColumnConfiguration: LogColumnConfigurationProps[]; - addLogColumn: (logColumn: LogColumnConfiguration) => void; - moveLogColumn: (sourceIndex: number, destinationIndex: number) => void; -} - -export const LogColumnsConfigurationPanel: React.FunctionComponent = ({ - addLogColumn, - moveLogColumn, - availableFields, - isLoading, - logColumnConfiguration, -}) => { - const onDragEnd = useCallback( - ({ source, destination }: DropResult) => - destination && moveLogColumn(source.index, destination.index), - [moveLogColumn] - ); - - return ( - - - - -

- -

-
-
- - - -
- {logColumnConfiguration.length > 0 ? ( - - - <> - {/* Fragment here necessary for typechecking */} - {logColumnConfiguration.map((column, index) => ( - - {(provided) => ( - - )} - - ))} - - - - ) : ( - - )} -
- ); -}; - -interface LogColumnConfigurationPanelProps { - logColumnConfigurationProps: LogColumnConfigurationProps; - dragHandleProps: DragHandleProps; -} - -const LogColumnConfigurationPanel: React.FunctionComponent = ( - props -) => ( - <> - - {props.logColumnConfigurationProps.type === 'timestamp' ? ( - - ) : props.logColumnConfigurationProps.type === 'message' ? ( - - ) : ( - - )} - -); - -const TimestampLogColumnConfigurationPanel: React.FunctionComponent = ({ - logColumnConfigurationProps, - dragHandleProps, -}) => ( - timestamp, - }} - /> - } - removeColumn={logColumnConfigurationProps.remove} - dragHandleProps={dragHandleProps} - /> -); - -const MessageLogColumnConfigurationPanel: React.FunctionComponent = ({ - logColumnConfigurationProps, - dragHandleProps, -}) => ( - - } - removeColumn={logColumnConfigurationProps.remove} - dragHandleProps={dragHandleProps} - /> -); - -const FieldLogColumnConfigurationPanel: React.FunctionComponent<{ - logColumnConfigurationProps: FieldLogColumnConfigurationProps; - dragHandleProps: DragHandleProps; -}> = ({ - logColumnConfigurationProps: { - logColumnConfiguration: { field }, - remove, - }, - dragHandleProps, -}) => { - const fieldLogColumnTitle = i18n.translate( - 'xpack.infra.sourceConfiguration.fieldLogColumnTitle', - { - defaultMessage: 'Field', - } - ); - return ( - - - -
- -
-
- {fieldLogColumnTitle} - - {field} - - - - -
-
- ); -}; - -const ExplainedLogColumnConfigurationPanel: React.FunctionComponent<{ - fieldName: React.ReactNode; - helpText: React.ReactNode; - removeColumn: () => void; - dragHandleProps: DragHandleProps; -}> = ({ fieldName, helpText, removeColumn, dragHandleProps }) => ( - - - -
- -
-
- {fieldName} - - - {helpText} - - - - - -
-
-); - -const RemoveLogColumnButton: React.FunctionComponent<{ - onClick?: () => void; - columnDescription: string; -}> = ({ onClick, columnDescription }) => { - const removeColumnLabel = i18n.translate( - 'xpack.infra.sourceConfiguration.removeLogColumnButtonLabel', - { - defaultMessage: 'Remove {columnDescription} column', - values: { columnDescription }, - } - ); - - return ( - - ); -}; - -const LogColumnConfigurationEmptyPrompt: React.FunctionComponent = () => ( - - - - } - body={ -

- -

- } - /> -); diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx index 43bdc1f4cedc..53b62f8dda04 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx @@ -22,19 +22,16 @@ import { Source } from '../../containers/source'; import { FieldsConfigurationPanel } from './fields_configuration_panel'; import { IndicesConfigurationPanel } from './indices_configuration_panel'; import { NameConfigurationPanel } from './name_configuration_panel'; -import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel'; import { useSourceConfigurationFormState } from './source_configuration_form_state'; import { SourceLoadingPage } from '../source_loading_page'; import { Prompt } from '../../utils/navigation_warning_prompt'; interface SourceConfigurationSettingsProps { shouldAllowEdit: boolean; - displaySettings: 'metrics' | 'logs'; } export const SourceConfigurationSettings = ({ shouldAllowEdit, - displaySettings, }: SourceConfigurationSettingsProps) => { const { createSourceConfiguration, @@ -45,16 +42,8 @@ export const SourceConfigurationSettings = ({ updateSourceConfiguration, } = useContext(Source.Context); - const availableFields = useMemo( - () => (source && source.status ? source.status.indexFields.map((field) => field.name) : []), - [source] - ); - const { - addLogColumn, - moveLogColumn, indicesConfigurationProps, - logColumnConfigurationProps, errors, resetForm, isFormDirty, @@ -119,10 +108,8 @@ export const SourceConfigurationSettings = ({ @@ -133,23 +120,10 @@ export const SourceConfigurationSettings = ({ isLoading={isLoading} podFieldProps={indicesConfigurationProps.podField} readOnly={!isWriteable} - tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField} timestampFieldProps={indicesConfigurationProps.timestampField} - displaySettings={displaySettings} /> - {displaySettings === 'logs' && ( - - - - )} {errors.length > 0 ? ( <> diff --git a/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.tsx b/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.tsx index 83effaa3d51a..b1dc55fe5c18 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.tsx @@ -62,7 +62,7 @@ export const IndicesConfigurationPanel = ({ id="xpack.infra.sourceConfiguration.logIndicesRecommendedValue" defaultMessage="The recommended value is {defaultValue}" values={{ - defaultValue: filebeat-*, + defaultValue: logs-*,filebeat-*, }} /> } diff --git a/x-pack/plugins/infra/public/pages/metrics/settings.tsx b/x-pack/plugins/infra/public/pages/metrics/settings.tsx index 7d4f35b19da7..b0aa67b5f081 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/settings.tsx @@ -15,7 +15,6 @@ export const MetricsSettingsPage = () => { ); diff --git a/x-pack/plugins/infra/server/lib/sources/defaults.ts b/x-pack/plugins/infra/server/lib/sources/defaults.ts index ba22b4db62d6..b096bed84fa9 100644 --- a/x-pack/plugins/infra/server/lib/sources/defaults.ts +++ b/x-pack/plugins/infra/server/lib/sources/defaults.ts @@ -9,8 +9,8 @@ import { InfraSourceConfiguration } from '../../../common/http_api/source_api'; export const defaultSourceConfiguration: InfraSourceConfiguration = { name: 'Default', description: '', - metricAlias: 'metricbeat-*', - logAlias: 'filebeat-*,kibana_sample_data_logs*', + metricAlias: 'metrics-*,metricbeat-*', + logAlias: 'logs-*,filebeat-*,kibana_sample_data_logs*', fields: { container: 'container.id', host: 'host.name', diff --git a/x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.test.ts b/x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.test.ts new file mode 100644 index 000000000000..59a22d33de85 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { migrationMocks } from 'src/core/server/mocks'; +import { addNewIndexingStrategyIndexNames } from './7_9_0_add_new_indexing_strategy_index_names'; +import { infraSourceConfigurationSavedObjectName } from '../saved_object_type'; + +describe('infra source configuration migration function for 7.9.0', () => { + test('adds "logs-*" when the logAlias contains "filebeat-*"', () => { + const unmigratedConfiguration = createTestSourceConfiguration( + 'filebeat-*,custom-log-index-*', + 'custom-metric-index-*' + ); + + const migratedConfiguration = addNewIndexingStrategyIndexNames( + unmigratedConfiguration, + migrationMocks.createContext() + ); + + expect(migratedConfiguration).toStrictEqual( + createTestSourceConfiguration('filebeat-*,custom-log-index-*,logs-*', 'custom-metric-index-*') + ); + }); + + test('doesn\'t add "logs-*" when the logAlias doesn\'t contain "filebeat-*"', () => { + const unmigratedConfiguration = createTestSourceConfiguration( + 'custom-log-index-*', + 'custom-metric-index-*' + ); + + const migratedConfiguration = addNewIndexingStrategyIndexNames( + unmigratedConfiguration, + migrationMocks.createContext() + ); + + expect(migratedConfiguration).toStrictEqual(unmigratedConfiguration); + }); + + test('doesn\'t add "logs-*" when the logAlias already contains it', () => { + const unmigratedConfiguration = createTestSourceConfiguration( + 'filebeat-*,logs-*,custom-log-index-*', + 'custom-metric-index-*' + ); + + const migratedConfiguration = addNewIndexingStrategyIndexNames( + unmigratedConfiguration, + migrationMocks.createContext() + ); + + expect(migratedConfiguration).toStrictEqual(unmigratedConfiguration); + }); + + test('adds "metrics-*" when the logAlias contains "metricbeat-*"', () => { + const unmigratedConfiguration = createTestSourceConfiguration( + 'custom-log-index-*', + 'metricbeat-*,custom-metric-index-*' + ); + + const migratedConfiguration = addNewIndexingStrategyIndexNames( + unmigratedConfiguration, + migrationMocks.createContext() + ); + + expect(migratedConfiguration).toStrictEqual( + createTestSourceConfiguration( + 'custom-log-index-*', + 'metricbeat-*,custom-metric-index-*,metrics-*' + ) + ); + }); + + test('doesn\'t add "metrics-*" when the logAlias doesn\'t contain "metricbeat-*"', () => { + const unmigratedConfiguration = createTestSourceConfiguration( + 'custom-log-index-*', + 'custom-metric-index-*' + ); + + const migratedConfiguration = addNewIndexingStrategyIndexNames( + unmigratedConfiguration, + migrationMocks.createContext() + ); + + expect(migratedConfiguration).toStrictEqual(unmigratedConfiguration); + }); + + test('doesn\'t add "metrics-*" when the metricAlias already contains it', () => { + const unmigratedConfiguration = createTestSourceConfiguration( + 'custom-log-index-*', + 'metrics-*,metricbeat-*,custom-metric-index-*' + ); + + const migratedConfiguration = addNewIndexingStrategyIndexNames( + unmigratedConfiguration, + migrationMocks.createContext() + ); + + expect(migratedConfiguration).toStrictEqual(unmigratedConfiguration); + }); +}); + +const createTestSourceConfiguration = (logAlias: string, metricAlias: string) => ({ + attributes: { + name: 'TEST CONFIGURATION', + description: '', + fields: { + pod: 'TEST POD FIELD', + host: 'TEST HOST FIELD', + message: ['TEST MESSAGE FIELD'], + container: 'TEST CONTAINER FIELD', + timestamp: 'TEST TIMESTAMP FIELD', + tiebreaker: 'TEST TIEBREAKER FIELD', + }, + inventoryDefaultView: '0', + metricsExplorerDefaultView: '0', + logColumns: [ + { + fieldColumn: { + id: 'TEST FIELD COLUMN ID', + field: 'TEST FIELD COLUMN FIELD', + }, + }, + ], + logAlias, + metricAlias, + }, + id: 'TEST_ID', + type: infraSourceConfigurationSavedObjectName, +}); diff --git a/x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.ts b/x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.ts new file mode 100644 index 000000000000..0d5563191d1b --- /dev/null +++ b/x-pack/plugins/infra/server/lib/sources/migrations/7_9_0_add_new_indexing_strategy_index_names.ts @@ -0,0 +1,36 @@ +/* + * 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 { SavedObjectMigrationFn } from 'src/core/server'; +import { InfraSourceConfiguration } from '../../../../common/http_api/source_api'; + +export const addNewIndexingStrategyIndexNames: SavedObjectMigrationFn< + InfraSourceConfiguration, + InfraSourceConfiguration +> = (sourceConfigurationDocument) => { + const oldLogAliasSegments = sourceConfigurationDocument.attributes.logAlias.split(','); + const oldMetricAliasSegments = sourceConfigurationDocument.attributes.metricAlias.split(','); + + const newLogAliasSegment = 'logs-*'; + const newMetricAliasSegment = 'metrics-*'; + + return { + ...sourceConfigurationDocument, + attributes: { + ...sourceConfigurationDocument.attributes, + logAlias: + oldLogAliasSegments.includes('filebeat-*') && + !oldLogAliasSegments.includes(newLogAliasSegment) + ? [...oldLogAliasSegments, newLogAliasSegment].join(',') + : sourceConfigurationDocument.attributes.logAlias, + metricAlias: + oldMetricAliasSegments.includes('metricbeat-*') && + !oldMetricAliasSegments.includes(newMetricAliasSegment) + ? [...oldMetricAliasSegments, newMetricAliasSegment].join(',') + : sourceConfigurationDocument.attributes.metricAlias, + }, + }; +}; diff --git a/x-pack/plugins/infra/server/lib/sources/saved_object_type.ts b/x-pack/plugins/infra/server/lib/sources/saved_object_type.ts index a36ef8d1a892..11db18d6bf79 100644 --- a/x-pack/plugins/infra/server/lib/sources/saved_object_type.ts +++ b/x-pack/plugins/infra/server/lib/sources/saved_object_type.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsType } from 'src/core/server'; +import { addNewIndexingStrategyIndexNames } from './migrations/7_9_0_add_new_indexing_strategy_index_names'; export const infraSourceConfigurationSavedObjectName = 'infrastructure-ui-source'; @@ -86,4 +86,7 @@ export const infraSourceConfigurationSavedObjectType: SavedObjectsType = { }, }, }, + migrations: { + '7.9.0': addNewIndexingStrategyIndexNames, + }, }; diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts b/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts index d4cdd7316b3f..00af3f8c2510 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_sources.ts @@ -35,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) { expect(origin).to.be('fallback'); expect(configuration.name).to.be('Default'); - expect(configuration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*'); + expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); expect(configuration.fields.timestamp).to.be('@timestamp'); expect(configuration.fields.tiebreaker).to.be('_doc'); expect(configuration.logColumns[0]).to.have.key('timestampColumn'); @@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { expect(configuration.name).to.be('Default'); expect(origin).to.be('stored'); - expect(configuration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*'); + expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); expect(configuration.fields.timestamp).to.be('@timestamp'); expect(configuration.fields.tiebreaker).to.be('_doc'); expect(configuration.logColumns).to.have.length(3); @@ -166,7 +166,7 @@ export default function ({ getService }: FtrProviderContext) { expect(configuration.name).to.be('NAME'); expect(origin).to.be('stored'); - expect(configuration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*'); + expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); expect(configuration.fields.timestamp).to.be('@timestamp'); expect(configuration.fields.tiebreaker).to.be('_doc'); expect(configuration.logColumns).to.have.length(3); diff --git a/x-pack/test/api_integration/apis/metrics_ui/sources.ts b/x-pack/test/api_integration/apis/metrics_ui/sources.ts index 5ed038776625..5908523af249 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/sources.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/sources.ts @@ -40,8 +40,8 @@ export default function ({ getService }: FtrProviderContext) { // shipped default values expect(sourceConfiguration.name).to.be('Default'); - expect(sourceConfiguration.metricAlias).to.be('metricbeat-*'); - expect(sourceConfiguration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*'); + expect(sourceConfiguration.metricAlias).to.be('metrics-*,metricbeat-*'); + expect(sourceConfiguration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); expect(sourceConfiguration.fields.container).to.be('container.id'); expect(sourceConfiguration.fields.host).to.be('host.name'); expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.uid'); @@ -125,8 +125,8 @@ export default function ({ getService }: FtrProviderContext) { expect(updatedAt).to.be.greaterThan(0); expect(configuration.name).to.be('NAME'); expect(configuration.description).to.be(''); - expect(configuration.metricAlias).to.be('metricbeat-*'); - expect(configuration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*'); + expect(configuration.metricAlias).to.be('metrics-*,metricbeat-*'); + expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); expect(configuration.fields.container).to.be('container.id'); expect(configuration.fields.host).to.be('host.name'); expect(configuration.fields.pod).to.be('kubernetes.pod.uid'); @@ -283,7 +283,7 @@ export default function ({ getService }: FtrProviderContext) { expect(version).to.not.be(initialVersion); expect(updatedAt).to.be.greaterThan(createdAt); expect(configuration.metricAlias).to.be('metricbeat-**'); - expect(configuration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*'); + expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); expect(status.logIndicesExist).to.be(true); expect(status.metricIndicesExist).to.be(true); }); From f28d4e920e7c86f24eec3f4c899c271087c6fe57 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 6 Jul 2020 09:40:07 -0700 Subject: [PATCH 02/21] Clean-up expression "run" methods (#70795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 remove legacy "interpret" methods in expressions * fix: 🐛 correctly use the moved "interpret" method * feat: 🎸 pass through execution context to sub-expressions * chore: 🤖 remove not used imports --- .../expressions/common/execution/execution.ts | 29 +++++++++++++++-- .../expressions/common/executor/executor.ts | 31 +------------------ 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 7bfb14b8bfa1..8df9f08e9c40 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -18,7 +18,7 @@ */ import { keys, last, mapValues, reduce, zipObject } from 'lodash'; -import { Executor } from '../executor'; +import { Executor, ExpressionExecOptions } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; import { Defer, now } from '../../../kibana_utils/common'; @@ -31,6 +31,7 @@ import { parse, formatExpression, parseExpression, + ExpressionAstNode, } from '../ast'; import { ExecutionContext, DefaultInspectorAdapters } from './types'; import { getType, ExpressionValue } from '../expression_types'; @@ -382,7 +383,7 @@ export class Execution< const resolveArgFns = mapValues(argAstsWithDefaults, (asts, argName) => { return asts.map((item: ExpressionAstExpression) => { return async (subInput = input) => { - const output = await this.params.executor.interpret(item, subInput, { + const output = await this.interpret(item, subInput, { debug: this.params.debug, }); if (isExpressionValueError(output)) throw output.error; @@ -415,4 +416,28 @@ export class Execution< // function which would be treated as a promise return { resolvedArgs }; } + + public async interpret( + ast: ExpressionAstNode, + input: T, + options?: ExpressionExecOptions + ): Promise { + switch (getType(ast)) { + case 'expression': + const execution = this.params.executor.createExecution( + ast as ExpressionAstExpression, + this.context, + options + ); + execution.start(input); + return await execution.result; + case 'string': + case 'number': + case 'null': + case 'boolean': + return ast; + default: + throw new Error(`Unknown AST object: ${JSON.stringify(ast)}`); + } + } } diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index 2ecbc5f75a9e..2b5f9f2556d8 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -26,8 +26,7 @@ import { Execution, ExecutionParams } from '../execution/execution'; import { IRegistry } from '../types'; import { ExpressionType } from '../expression_types/expression_type'; import { AnyExpressionTypeDefinition } from '../expression_types/types'; -import { getType } from '../expression_types'; -import { ExpressionAstExpression, ExpressionAstNode } from '../ast'; +import { ExpressionAstExpression } from '../ast'; import { typeSpecs } from '../expression_types/specs'; import { functionSpecs } from '../expression_functions/specs'; @@ -154,34 +153,6 @@ export class Executor = Record( - ast: ExpressionAstNode, - input: T, - options?: ExpressionExecOptions - ): Promise { - switch (getType(ast)) { - case 'expression': - return await this.interpretExpression(ast as ExpressionAstExpression, input, options); - case 'string': - case 'number': - case 'null': - case 'boolean': - return ast; - default: - throw new Error(`Unknown AST object: ${JSON.stringify(ast)}`); - } - } - - public async interpretExpression( - ast: string | ExpressionAstExpression, - input: T, - options?: ExpressionExecOptions - ): Promise { - const execution = this.createExecution(ast, undefined, options); - execution.start(input); - return await execution.result; - } - /** * Execute expression and return result. * From cbd39d98a60feb9007fb8602fca1a584ae1729f2 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Mon, 6 Jul 2020 09:45:51 -0700 Subject: [PATCH 03/21] [Ingest Manager] Implement concurrency control for package configs (#70680) * Send SO version field as part of package configs, enforce it during package config update * Fix typings, extend response error to include optional status code * Revert unnecessary version fields in tests, fix schema Co-authored-by: Elastic Machine --- .../common/types/models/package_config.ts | 7 +++- .../common/types/rest_spec/package_config.ts | 4 +- .../hooks/use_request/use_request.ts | 22 +++++++---- .../edit_package_config_page/index.tsx | 37 +++++++++++++++---- .../ingest_manager/types/index.ts | 1 + .../server/routes/package_config/handlers.ts | 2 +- .../server/services/package_config.ts | 32 +++++++++++----- .../ingest_manager/server/types/index.tsx | 1 + .../server/types/models/package_config.ts | 6 +++ .../server/types/rest_spec/package_config.ts | 4 +- 10 files changed, 86 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_config.ts b/x-pack/plugins/ingest_manager/common/types/models/package_config.ts index e9595bab0174..0ff56e6d05d3 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/package_config.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/package_config.ts @@ -55,9 +55,14 @@ export interface NewPackageConfig { inputs: NewPackageConfigInput[]; } +export interface UpdatePackageConfig extends NewPackageConfig { + version?: string; +} + export interface PackageConfig extends Omit { id: string; inputs: PackageConfigInput[]; + version?: string; revision: number; updated_at: string; updated_by: string; @@ -65,4 +70,4 @@ export interface PackageConfig extends Omit { created_by: string; } -export type PackageConfigSOAttributes = Omit; +export type PackageConfigSOAttributes = Omit; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts index 4b8abbde47d5..e62645debb74 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.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 { PackageConfig, NewPackageConfig } from '../models'; +import { PackageConfig, NewPackageConfig, UpdatePackageConfig } from '../models'; export interface GetPackageConfigsRequest { query: { @@ -42,7 +42,7 @@ export interface CreatePackageConfigResponse { } export type UpdatePackageConfigRequest = GetOnePackageConfigRequest & { - body: NewPackageConfig; + body: UpdatePackageConfig; }; export type UpdatePackageConfigResponse = CreatePackageConfigResponse; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts index fbbc482fb96a..1486c2e50b7a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts @@ -17,33 +17,39 @@ let httpClient: HttpSetup; export type UseRequestConfig = _UseRequestConfig; +interface RequestError extends Error { + statusCode?: number; +} + export const setHttpClient = (client: HttpSetup) => { httpClient = client; }; -export const sendRequest = ( +export const sendRequest = ( config: SendRequestConfig -): Promise> => { +): Promise> => { if (!httpClient) { throw new Error('sendRequest has no http client set'); } - return _sendRequest(httpClient, config); + return _sendRequest(httpClient, config); }; -export const useRequest = (config: UseRequestConfig) => { +export const useRequest = (config: UseRequestConfig) => { if (!httpClient) { throw new Error('sendRequest has no http client set'); } - return _useRequest(httpClient, config); + return _useRequest(httpClient, config); }; export type SendConditionalRequestConfig = | (SendRequestConfig & { shouldSendRequest: true }) | (Partial & { shouldSendRequest: false }); -export const useConditionalRequest = (config: SendConditionalRequestConfig) => { +export const useConditionalRequest = ( + config: SendConditionalRequestConfig +) => { const [state, setState] = useState<{ - error: Error | null; + error: RequestError | null; data: D | null; isLoading: boolean; }>({ @@ -70,7 +76,7 @@ export const useConditionalRequest = (config: SendConditionalRequestCon isLoading: true, error: null, }); - const res = await sendRequest({ + const res = await sendRequest({ method: config.method, path: config.path, query: config.query, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/edit_package_config_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/edit_package_config_page/index.tsx index 7fbcdbb9738c..52fd95d66367 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/edit_package_config_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/edit_package_config_page/index.tsx @@ -16,7 +16,7 @@ import { EuiFlexItem, EuiSpacer, } from '@elastic/eui'; -import { AgentConfig, PackageInfo, NewPackageConfig } from '../../../types'; +import { AgentConfig, PackageInfo, UpdatePackageConfig } from '../../../types'; import { useLink, useBreadcrumbs, @@ -72,7 +72,7 @@ export const EditPackageConfigPage: React.FunctionComponent = () => { const [loadingError, setLoadingError] = useState(); const [agentConfig, setAgentConfig] = useState(); const [packageInfo, setPackageInfo] = useState(); - const [packageConfig, setPackageConfig] = useState({ + const [packageConfig, setPackageConfig] = useState({ name: '', description: '', namespace: '', @@ -80,6 +80,7 @@ export const EditPackageConfigPage: React.FunctionComponent = () => { enabled: true, output_id: '', inputs: [], + version: '', }); // Retrieve agent config, package, and package config info @@ -160,7 +161,7 @@ export const EditPackageConfigPage: React.FunctionComponent = () => { const hasErrors = validationResults ? validationHasErrors(validationResults) : false; // Update package config method - const updatePackageConfig = (updatedFields: Partial) => { + const updatePackageConfig = (updatedFields: Partial) => { const newPackageConfig = { ...packageConfig, ...updatedFields, @@ -178,7 +179,7 @@ export const EditPackageConfigPage: React.FunctionComponent = () => { } }; - const updatePackageConfigValidation = (newPackageConfig?: NewPackageConfig) => { + const updatePackageConfigValidation = (newPackageConfig?: UpdatePackageConfig) => { if (packageInfo) { const newValidationResult = validatePackageConfig( newPackageConfig || packageConfig, @@ -234,9 +235,31 @@ export const EditPackageConfigPage: React.FunctionComponent = () => { : undefined, }); } else { - notifications.toasts.addError(error, { - title: 'Error', - }); + if (error.statusCode === 409) { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.ingestManager.editPackageConfig.failedNotificationTitle', { + defaultMessage: `Error updating '{packageConfigName}'`, + values: { + packageConfigName: packageConfig.name, + }, + }), + toastMessage: i18n.translate( + 'xpack.ingestManager.editPackageConfig.failedConflictNotificationMessage', + { + defaultMessage: `Data is out of date. Refresh the page to get the latest configuration.`, + } + ), + }); + } else { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.ingestManager.editPackageConfig.failedNotificationTitle', { + defaultMessage: `Error updating '{packageConfigName}'`, + values: { + packageConfigName: packageConfig.name, + }, + }), + }); + } setFormState('VALID'); } }; 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 43ec2f6d1a74..e28d76cae995 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 @@ -15,6 +15,7 @@ export { EnrollmentAPIKey, PackageConfig, NewPackageConfig, + UpdatePackageConfig, PackageConfigInput, PackageConfigInputStream, PackageConfigConfigRecordEntry, diff --git a/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts index e212c861ce77..f11275c92bb6 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts @@ -178,7 +178,7 @@ export const updatePackageConfigHandler: RequestHandler< }); } catch (e) { return response.customError({ - statusCode: 500, + statusCode: e.statusCode || 500, body: { message: e.message }, }); } diff --git a/x-pack/plugins/ingest_manager/server/services/package_config.ts b/x-pack/plugins/ingest_manager/server/services/package_config.ts index 5a7546bfee2e..9fa51d025ad2 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_config.ts @@ -15,6 +15,7 @@ import { import { PACKAGE_CONFIG_SAVED_OBJECT_TYPE } from '../constants'; import { NewPackageConfig, + UpdatePackageConfig, PackageConfig, ListWithKuery, PackageConfigSOAttributes, @@ -60,6 +61,7 @@ class PackageConfigService { return { id: newSo.id, + version: newSo.version, ...newSo.attributes, }; } @@ -71,7 +73,7 @@ class PackageConfigService { options?: { user?: AuthenticatedUser } ): Promise { const isoDate = new Date().toISOString(); - const { saved_objects: newSos } = await soClient.bulkCreate>( + const { saved_objects: newSos } = await soClient.bulkCreate( packageConfigs.map((packageConfig) => ({ type: SAVED_OBJECT_TYPE, attributes: { @@ -98,6 +100,7 @@ class PackageConfigService { return newSos.map((newSo) => ({ id: newSo.id, + version: newSo.version, ...newSo.attributes, })); } @@ -117,6 +120,7 @@ class PackageConfigService { return { id: packageConfigSO.id, + version: packageConfigSO.version, ...packageConfigSO.attributes, }; } @@ -137,6 +141,7 @@ class PackageConfigService { return packageConfigSO.saved_objects.map((so) => ({ id: so.id, + version: so.version, ...so.attributes, })); } @@ -163,8 +168,9 @@ class PackageConfigService { }); return { - items: packageConfigs.saved_objects.map((packageConfigSO) => ({ + items: packageConfigs.saved_objects.map((packageConfigSO) => ({ id: packageConfigSO.id, + version: packageConfigSO.version, ...packageConfigSO.attributes, })), total: packageConfigs.total, @@ -176,21 +182,29 @@ class PackageConfigService { public async update( soClient: SavedObjectsClientContract, id: string, - packageConfig: NewPackageConfig, + packageConfig: UpdatePackageConfig, options?: { user?: AuthenticatedUser } ): Promise { const oldPackageConfig = await this.get(soClient, id); + const { version, ...restOfPackageConfig } = packageConfig; if (!oldPackageConfig) { throw new Error('Package config not found'); } - await soClient.update(SAVED_OBJECT_TYPE, id, { - ...packageConfig, - revision: oldPackageConfig.revision + 1, - updated_at: new Date().toISOString(), - updated_by: options?.user?.username ?? 'system', - }); + await soClient.update( + SAVED_OBJECT_TYPE, + id, + { + ...restOfPackageConfig, + revision: oldPackageConfig.revision + 1, + updated_at: new Date().toISOString(), + updated_by: options?.user?.username ?? 'system', + }, + { + version, + } + ); // Bump revision of associated agent config await agentConfigService.bumpRevision(soClient, packageConfig.config_id, { diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 179474d31bc1..8239302a9783 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -21,6 +21,7 @@ export { PackageConfigInput, PackageConfigInputStream, NewPackageConfig, + UpdatePackageConfig, PackageConfigSOAttributes, FullAgentConfigInput, FullAgentConfig, diff --git a/x-pack/plugins/ingest_manager/server/types/models/package_config.ts b/x-pack/plugins/ingest_manager/server/types/models/package_config.ts index 4b9718dfbe16..0823ccd85a32 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/package_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/package_config.ts @@ -66,7 +66,13 @@ export const NewPackageConfigSchema = schema.object({ ...PackageConfigBaseSchema, }); +export const UpdatePackageConfigSchema = schema.object({ + ...PackageConfigBaseSchema, + version: schema.maybe(schema.string()), +}); + export const PackageConfigSchema = schema.object({ ...PackageConfigBaseSchema, id: schema.string(), + version: schema.maybe(schema.string()), }); diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/package_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/package_config.ts index 7b7ae1957c15..630fb55f2654 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/package_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/package_config.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { NewPackageConfigSchema } from '../models'; +import { NewPackageConfigSchema, UpdatePackageConfigSchema } from '../models'; import { ListWithKuerySchema } from './index'; export const GetPackageConfigsRequestSchema = { @@ -23,7 +23,7 @@ export const CreatePackageConfigRequestSchema = { export const UpdatePackageConfigRequestSchema = { ...GetOnePackageConfigRequestSchema, - body: NewPackageConfigSchema, + body: UpdatePackageConfigSchema, }; export const DeletePackageConfigsRequestSchema = { From a4485c86c1613bfd560f9d88c6d3c02614f3e83c Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 6 Jul 2020 12:53:27 -0400 Subject: [PATCH 04/21] [Ingest Manager] Fix agent version check to work with SNAPSHOT versions (#70796) --- .../server/services/agents/enroll.test.ts | 66 +++++++++++++++++++ .../server/services/agents/enroll.ts | 28 ++++++-- 2 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts new file mode 100644 index 000000000000..764564cfa49f --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { validateAgentVersion } from './enroll'; +import { appContextService } from '../app_context'; +import { IngestManagerAppContext } from '../../plugin'; + +describe('validateAgentVersion', () => { + it('should throw with agent > kibana version', () => { + appContextService.start(({ + kibanaVersion: '8.0.0', + } as unknown) as IngestManagerAppContext); + expect(() => + validateAgentVersion({ + local: { elastic: { agent: { version: '8.8.0' } } }, + userProvided: {}, + }) + ).toThrowError(/Agent version is not compatible with kibana version/); + }); + it('should work with agent < kibana version', () => { + appContextService.start(({ + kibanaVersion: '8.0.0', + } as unknown) as IngestManagerAppContext); + validateAgentVersion({ local: { elastic: { agent: { version: '7.8.0' } } }, userProvided: {} }); + }); + + it('should work with agent = kibana version', () => { + appContextService.start(({ + kibanaVersion: '8.0.0', + } as unknown) as IngestManagerAppContext); + validateAgentVersion({ local: { elastic: { agent: { version: '8.0.0' } } }, userProvided: {} }); + }); + + it('should work with SNAPSHOT version', () => { + appContextService.start(({ + kibanaVersion: '8.0.0-SNAPSHOT', + } as unknown) as IngestManagerAppContext); + validateAgentVersion({ + local: { elastic: { agent: { version: '8.0.0-SNAPSHOT' } } }, + userProvided: {}, + }); + }); + + it('should work with a agent using SNAPSHOT version', () => { + appContextService.start(({ + kibanaVersion: '7.8.0', + } as unknown) as IngestManagerAppContext); + validateAgentVersion({ + local: { elastic: { agent: { version: '7.8.0-SNAPSHOT' } } }, + userProvided: {}, + }); + }); + + it('should work with a kibana using SNAPSHOT version', () => { + appContextService.start(({ + kibanaVersion: '7.8.0-SNAPSHOT', + } as unknown) as IngestManagerAppContext); + validateAgentVersion({ + local: { elastic: { agent: { version: '7.8.0' } } }, + userProvided: {}, + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts index bf15815e6ae4..b63b1c13e4df 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts @@ -20,11 +20,7 @@ export async function enroll( metadata?: { local: any; userProvided: any }, sharedId?: string ): Promise { - const kibanaVersion = appContextService.getKibanaVersion(); - const version: string | undefined = metadata?.local?.elastic?.agent?.version; - if (!version || semver.compare(version, kibanaVersion) === 1) { - throw Boom.badRequest('Agent version is not compatible with kibana version'); - } + validateAgentVersion(metadata); const existingAgent = sharedId ? await getAgentBySharedId(soClient, sharedId) : null; @@ -92,3 +88,25 @@ async function getAgentBySharedId(soClient: SavedObjectsClientContract, sharedId return null; } + +export function validateAgentVersion(metadata?: { local: any; userProvided: any }) { + const kibanaVersion = semver.parse(appContextService.getKibanaVersion()); + if (!kibanaVersion) { + throw Boom.badRequest('Kibana version is not set'); + } + const version = semver.parse(metadata?.local?.elastic?.agent?.version); + if (!version) { + throw Boom.badRequest('Agent version not provided in metadata.'); + } + + if (!version || !semver.lte(formatVersion(version), formatVersion(kibanaVersion))) { + throw Boom.badRequest('Agent version is not compatible with kibana version'); + } +} + +/** + * used to remove prelease from version as includePrerelease in not working as expected + */ +function formatVersion(version: semver.SemVer) { + return `${version.major}.${version.minor}.${version.patch}`; +} From 89dcdbbbee08b2b464cf64164b001c7a82945a7e Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Mon, 6 Jul 2020 18:57:18 +0200 Subject: [PATCH 05/21] [Ingest Manager] Update package registry docker image for CI. (#70716) * Update package registry docker image for CI. * Adapt to new registry filesystem layout. * Adjust tests to changed registry behavior. Co-authored-by: Elastic Machine --- .../test/ingest_manager_api_integration/apis/file.ts | 6 +++--- .../apis/fixtures/package_registry_config.yml | 4 ++-- x-pack/test/ingest_manager_api_integration/config.ts | 10 +++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/test/ingest_manager_api_integration/apis/file.ts b/x-pack/test/ingest_manager_api_integration/apis/file.ts index 33eeda1ee274..a7462ac51ecc 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/file.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/file.ts @@ -47,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { '/api/ingest_manager/epm/packages/filetest/0.1.0/kibana/visualization/sample_visualization.json' ) .set('kbn-xsrf', 'xxx') - .expect('Content-Type', 'application/json; charset=utf-8') + .expect('Content-Type', 'text/plain; charset=utf-8') .expect(200); } else { warnAndSkipTest(this, log); @@ -61,7 +61,7 @@ export default function ({ getService }: FtrProviderContext) { '/api/ingest_manager/epm/packages/filetest/0.1.0/kibana/dashboard/sample_dashboard.json' ) .set('kbn-xsrf', 'xxx') - .expect('Content-Type', 'application/json; charset=utf-8') + .expect('Content-Type', 'text/plain; charset=utf-8') .expect(200); } else { warnAndSkipTest(this, log); @@ -73,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest .get('/api/ingest_manager/epm/packages/filetest/0.1.0/kibana/search/sample_search.json') .set('kbn-xsrf', 'xxx') - .expect('Content-Type', 'application/json; charset=utf-8') + .expect('Content-Type', 'text/plain; charset=utf-8') .expect(200); } else { warnAndSkipTest(this, log); diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/package_registry_config.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/package_registry_config.yml index 0060e247827d..5bfbf78e25ed 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/package_registry_config.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/package_registry_config.yml @@ -1,3 +1,3 @@ package_paths: - - /registry/packages/package-storage - - /registry/packages/test-packages \ No newline at end of file + - /packages/production + - /packages/test-packages \ No newline at end of file diff --git a/x-pack/test/ingest_manager_api_integration/config.ts b/x-pack/test/ingest_manager_api_integration/config.ts index bbef12463ed0..88ec8d53c1cd 100644 --- a/x-pack/test/ingest_manager_api_integration/config.ts +++ b/x-pack/test/ingest_manager_api_integration/config.ts @@ -21,21 +21,25 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `${path.join( path.dirname(__filename), './apis/fixtures/package_registry_config.yml' - )}:/registry/config.yml`, + )}:/package-registry/config.yml`, '-v', `${path.join( path.dirname(__filename), './apis/fixtures/test_packages' - )}:/registry/packages/test-packages`, + )}:/packages/test-packages`, ]; + // Docker image to use for Ingest Manager API integration tests. + const dockerImage = + 'docker.elastic.co/package-registry/distribution:184b85f19e8fd14363e36150173d338ff9659f01'; + return { testFiles: [require.resolve('./apis')], servers: xPackAPITestsConfig.get('servers'), dockerServers: defineDockerServersConfig({ registry: { enabled: !!registryPort, - image: 'docker.elastic.co/package-registry/package-registry:kibana-testing-1', + image: dockerImage, portInContainer: 8080, port: registryPort, args: dockerArgs, From 31abd6dc28d723d71378920bd1d01940444148be Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 6 Jul 2020 13:10:01 -0400 Subject: [PATCH 06/21] [ML] DF Analytics creation: switch to includes table (#70009) * update modelMemoryLimit when hyperParams change * update functional clone tests * switch excludes table to includes table * Job configuration details update * fix jest tests and types * fix translations and validate includes fields * fix functional test * handle empty includes selection * switch filter to field_value_toggle_group * update clone functional test --- .../custom_selection_table.js | 13 +- .../advanced_step/advanced_step.tsx | 15 +- .../advanced_step/advanced_step_form.tsx | 11 +- .../analysis_fields_table.tsx | 282 ++++++++++-------- .../configuration_step/configuration_step.tsx | 18 +- .../configuration_step_details.tsx | 15 +- .../configuration_step_form.tsx | 61 ++-- .../configuration_step/job_type.tsx | 2 +- .../components/create_step/create_step.tsx | 6 +- .../components/details_step/details_step.tsx | 15 +- .../pages/analytics_creation/page.tsx | 3 - .../analytics_list/action_clone.test.ts | 40 ++- .../analytics_list/action_clone.tsx | 2 +- .../use_create_analytics_form/reducer.ts | 19 +- .../use_create_analytics_form/state.test.ts | 54 +++- .../hooks/use_create_analytics_form/state.ts | 77 +---- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../classification_creation.ts | 9 + .../apps/ml/data_frame_analytics/cloning.ts | 10 +- .../apps/ml/data_frame_analytics/index.ts | 2 +- .../outlier_detection_creation.ts | 9 + .../regression_creation.ts | 9 + .../ml/data_frame_analytics_creation.ts | 71 ++++- 24 files changed, 461 insertions(+), 286 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js index c86b716b2f49..274a5ff0ffbb 100644 --- a/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js +++ b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js @@ -45,6 +45,7 @@ function getError(error) { export function CustomSelectionTable({ checkboxDisabledCheck, columns, + currentPage = 0, filterDefaultFields, filters, items, @@ -52,6 +53,7 @@ export function CustomSelectionTable({ onTableChange, radioDisabledCheck, selectedIds, + setCurrentPaginationData, singleSelection, sortableProperties, tableItemId = 'id', @@ -80,7 +82,7 @@ export function CustomSelectionTable({ }, [selectedIds]); // eslint-disable-line useEffect(() => { - const tablePager = new Pager(currentItems.length, itemsPerPage); + const tablePager = new Pager(currentItems.length, itemsPerPage, currentPage); setPagerSettings({ itemsPerPage: itemsPerPage, firstItemIndex: tablePager.getFirstItemIndex(), @@ -124,6 +126,13 @@ export function CustomSelectionTable({ } } + if (setCurrentPaginationData) { + setCurrentPaginationData({ + pageIndex: pager.getCurrentPageIndex(), + itemsPerPage: pagerSettings.itemsPerPage, + }); + } + onTableChange(currentSelected); } @@ -389,6 +398,7 @@ export function CustomSelectionTable({ CustomSelectionTable.propTypes = { checkboxDisabledCheck: PropTypes.func, columns: PropTypes.array.isRequired, + currentPage: PropTypes.number, filterDefaultFields: PropTypes.array, filters: PropTypes.array, items: PropTypes.array.isRequired, @@ -396,6 +406,7 @@ CustomSelectionTable.propTypes = { onTableChange: PropTypes.func.isRequired, radioDisabledCheck: PropTypes.func, selectedId: PropTypes.array, + setCurrentPaginationData: PropTypes.func, singleSelection: PropTypes.bool, sortableProperties: PropTypes.object, tableItemId: PropTypes.string, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx index f957dcab2e87..b16300a448a7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx @@ -19,14 +19,19 @@ export const AdvancedStep: FC = ({ setCurrentStep, stepActivated, }) => { + const showForm = step === ANALYTICS_STEPS.ADVANCED; + const showDetails = step !== ANALYTICS_STEPS.ADVANCED && stepActivated === true; + + const dataTestSubj = `mlAnalyticsCreateJobWizardAdvancedStep${showForm ? ' active' : ''}${ + showDetails ? ' summary' : '' + }`; + return ( - - {step === ANALYTICS_STEPS.ADVANCED && ( + + {showForm && ( )} - {step !== ANALYTICS_STEPS.ADVANCED && stepActivated === true && ( - - )} + {showDetails && } ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx index bc9bb0cce5ae..21b0d3d7dd89 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx @@ -47,7 +47,7 @@ export const AdvancedStepForm: FC = ({ const [advancedParamErrors, setAdvancedParamErrors] = useState({}); const [fetchingAdvancedParamErrors, setFetchingAdvancedParamErrors] = useState(false); - const { setFormState } = actions; + const { setEstimatedModelMemoryLimit, setFormState } = actions; const { form, isJobCreated } = state; const { computeFeatureInfluence, @@ -87,10 +87,15 @@ export const AdvancedStepForm: FC = ({ useEffect(() => { setFetchingAdvancedParamErrors(true); (async function () { - const { success, errorMessage } = await fetchExplainData(form); + const { success, errorMessage, expectedMemory } = await fetchExplainData(form); const paramErrors: AdvancedParamErrors = {}; - if (!success) { + if (success) { + if (modelMemoryLimit !== expectedMemory) { + setEstimatedModelMemoryLimit(expectedMemory); + setFormState({ modelMemoryLimit: expectedMemory }); + } + } else { // Check which field is invalid Object.values(ANALYSIS_ADVANCED_FIELDS).forEach((param) => { if (errorMessage.includes(`[${param}]`)) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx index c71e7e73b13d..def6acdae14e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, memo, useEffect, useState } from 'react'; +import React, { FC, Fragment, useEffect, useState } from 'react'; import { EuiCallOut, EuiFormRow, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; // @ts-ignore no declaration import { LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services'; @@ -14,6 +14,13 @@ import { FieldSelectionItem } from '../../../../common/analytics'; // @ts-ignore could not find declaration file import { CustomSelectionTable } from '../../../../../components/custom_selection_table'; +const minimumFieldsMessage = i18n.translate( + 'xpack.ml.dataframe.analytics.create.analysisFieldsTable.minimumFieldsMessage', + { + defaultMessage: 'At least one field must be selected.', + } +); + const columns = [ { id: 'checkbox', @@ -22,9 +29,12 @@ const columns = [ width: '32px', }, { - label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.fieldNameColumn', { - defaultMessage: 'Field name', - }), + label: i18n.translate( + 'xpack.ml.dataframe.analytics.create.analysisFieldsTable.fieldNameColumn', + { + defaultMessage: 'Field name', + } + ), id: 'name', isSortable: true, alignment: LEFT_ALIGNMENT, @@ -68,140 +78,154 @@ const columns = [ ]; const checkboxDisabledCheck = (item: FieldSelectionItem) => - (item.is_included === false && !item.reason?.includes('in excludes list')) || - item.is_required === true; + item.is_required === true || (item.reason && item.reason.includes('unsupported type')); -export const MemoizedAnalysisFieldsTable: FC<{ - excludes: string[]; +export const AnalysisFieldsTable: FC<{ + dependentVariable?: string; + includes: string[]; loadingItems: boolean; - setFormState: any; + setFormState: React.Dispatch>; tableItems: FieldSelectionItem[]; -}> = memo( - ({ excludes, loadingItems, setFormState, tableItems }) => { - const [sortableProperties, setSortableProperties] = useState(); - const [currentSelection, setCurrentSelection] = useState([]); +}> = ({ dependentVariable, includes, loadingItems, setFormState, tableItems }) => { + const [sortableProperties, setSortableProperties] = useState(); + const [currentPaginationData, setCurrentPaginationData] = useState<{ + pageIndex: number; + itemsPerPage: number; + }>({ pageIndex: 0, itemsPerPage: 5 }); + const [minimumFieldsRequiredMessage, setMinimumFieldsRequiredMessage] = useState< + undefined | string + >(undefined); - useEffect(() => { - if (excludes.length > 0) { - setCurrentSelection(excludes); - } - }, [tableItems]); + useEffect(() => { + if (includes.length === 0 && tableItems.length > 0) { + const includedFields: string[] = []; + tableItems.forEach((field) => { + if (field.is_included === true) { + includedFields.push(field.name); + } + }); + setFormState({ includes: includedFields }); + } else if (includes.length > 0) { + setFormState({ includes }); + } + setMinimumFieldsRequiredMessage(undefined); + }, [tableItems]); - // Only set form state on unmount to prevent re-renders due to props changing if exludes was updated on each selection - useEffect(() => { - return () => { - setFormState({ excludes: currentSelection }); - }; - }, [currentSelection]); + useEffect(() => { + let sortablePropertyItems = []; + const defaultSortProperty = 'name'; - useEffect(() => { - let sortablePropertyItems = []; - const defaultSortProperty = 'name'; - - sortablePropertyItems = [ - { - name: 'name', - getValue: (item: any) => item.name.toLowerCase(), - isAscending: true, - }, - { - name: 'is_included', - getValue: (item: any) => item.is_included, - isAscending: true, - }, - { - name: 'is_required', - getValue: (item: any) => item.is_required, - isAscending: true, - }, - ]; - const sortableProps = new SortableProperties(sortablePropertyItems, defaultSortProperty); - - setSortableProperties(sortableProps); - }, []); - - const filters = [ + sortablePropertyItems = [ { - type: 'field_value_selection', - field: 'is_included', - name: i18n.translate('xpack.ml.dataframe.analytics.create.excludedFilterLabel', { - defaultMessage: 'Is included', - }), - multiSelect: false, - options: [ - { - value: true, - view: ( - - {i18n.translate('xpack.ml.dataframe.analytics.create.isIncludedOption', { - defaultMessage: 'Yes', - })} - - ), - }, - { - value: false, - view: ( - - {i18n.translate('xpack.ml.dataframe.analytics.create.isNotIncludedOption', { - defaultMessage: 'No', - })} - - ), - }, - ], + name: 'name', + getValue: (item: any) => item.name.toLowerCase(), + isAscending: true, + }, + { + name: 'is_included', + getValue: (item: any) => item.is_included, + isAscending: true, + }, + { + name: 'is_required', + getValue: (item: any) => item.is_required, + isAscending: true, }, ]; + const sortableProps = new SortableProperties(sortablePropertyItems, defaultSortProperty); - return ( - - + + + + {tableItems.length > 0 && minimumFieldsRequiredMessage === undefined && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.includedFieldsCount', { + defaultMessage: + '{numFields, plural, one {# field} other {# fields}} included in the analysis', + values: { numFields: includes.length }, + })} + + )} + {tableItems.length === 0 && ( + - - - {tableItems.length === 0 && ( - - - - )} - {tableItems.length > 0 && ( - - { - setCurrentSelection(selection); - }} - selectedIds={currentSelection} - singleSelection={false} - sortableProperties={sortableProperties} - tableItemId={'name'} - /> - - )} - - - ); - }, - (prevProps, nextProps) => prevProps.tableItems.length === nextProps.tableItems.length -); + + + )} + {tableItems.length > 0 && ( + + { + // dependent variable must always be in includes + if ( + dependentVariable !== undefined && + dependentVariable !== '' && + selection.length === 0 + ) { + selection = [dependentVariable]; + } + // If nothing selected show minimum fields required message and don't update form yet + if (selection.length === 0) { + setMinimumFieldsRequiredMessage(minimumFieldsMessage); + } else { + setMinimumFieldsRequiredMessage(undefined); + setFormState({ includes: selection }); + } + }} + selectedIds={includes} + setCurrentPaginationData={setCurrentPaginationData} + singleSelection={false} + sortableProperties={sortableProperties} + tableItemId={'name'} + /> + + )} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx index 220910535aaf..d818117c9d78 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx @@ -19,17 +19,19 @@ export const ConfigurationStep: FC = ({ step, stepActivated, }) => { + const showForm = step === ANALYTICS_STEPS.CONFIGURATION; + const showDetails = step !== ANALYTICS_STEPS.CONFIGURATION && stepActivated === true; + + const dataTestSubj = `mlAnalyticsCreateJobWizardConfigurationStep${showForm ? ' active' : ''}${ + showDetails ? ' summary' : '' + }`; + return ( - - {step === ANALYTICS_STEPS.CONFIGURATION && ( + + {showForm && ( )} - {step !== ANALYTICS_STEPS.CONFIGURATION && stepActivated === true && ( - - )} + {showDetails && } ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx index 6603af9aa302..193d7dcce7f5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx @@ -21,6 +21,8 @@ import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; import { useMlContext } from '../../../../../contexts/ml'; import { ANALYTICS_STEPS } from '../../page'; +const MAX_INCLUDES_LENGTH = 5; + interface Props { setCurrentStep: React.Dispatch>; state: State; @@ -30,7 +32,7 @@ export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) = const mlContext = useMlContext(); const { currentIndexPattern } = mlContext; const { form, isJobCreated } = state; - const { dependentVariable, excludes, jobConfigQueryString, jobType, trainingPercent } = form; + const { dependentVariable, includes, jobConfigQueryString, jobType, trainingPercent } = form; const isJobTypeWithDepVar = jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; @@ -61,10 +63,15 @@ export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) = const detailsThirdCol = [ { - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.excludedFields', { - defaultMessage: 'Excluded fields', + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.includedFields', { + defaultMessage: 'Included fields', }), - description: excludes.length > 0 ? excludes.join(', ') : UNSET_CONFIG_ITEM, + description: + includes.length > MAX_INCLUDES_LENGTH + ? `${includes.slice(0, MAX_INCLUDES_LENGTH).join(', ')} ... (and ${ + includes.length - MAX_INCLUDES_LENGTH + } more)` + : includes.join(', '), }, ]; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index 76378dc372f1..b83dd2e4329e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -39,7 +39,7 @@ import { ANALYTICS_STEPS } from '../../page'; import { ContinueButton } from '../continue_button'; import { JobType } from './job_type'; import { SupportedFieldsMessage } from './supported_fields_message'; -import { MemoizedAnalysisFieldsTable } from './analysis_fields_table'; +import { AnalysisFieldsTable } from './analysis_fields_table'; import { DataGrid } from '../../../../../components/data_grid'; import { fetchExplainData } from '../shared'; import { useIndexData } from '../../hooks'; @@ -49,7 +49,8 @@ import { useSavedSearch } from './use_saved_search'; const requiredFieldsErrorText = i18n.translate( 'xpack.ml.dataframe.analytics.createWizard.requiredFieldsErrorMessage', { - defaultMessage: 'At least one field must be included in the analysis.', + defaultMessage: + 'At least one field must be included in the analysis in addition to the dependent variable.', } ); @@ -69,17 +70,20 @@ export const ConfigurationStepForm: FC = ({ const [dependentVariableOptions, setDependentVariableOptions] = useState< EuiComboBoxOptionOption[] >([]); - const [excludesTableItems, setExcludesTableItems] = useState([]); + const [includesTableItems, setIncludesTableItems] = useState([]); const [maxDistinctValuesError, setMaxDistinctValuesError] = useState( undefined ); + const [unsupportedFieldsError, setUnsupportedFieldsError] = useState( + undefined + ); const { setEstimatedModelMemoryLimit, setFormState } = actions; const { estimatedModelMemoryLimit, form, isJobCreated, requestMessages } = state; const firstUpdate = useRef(true); const { dependentVariable, - excludes, + includes, jobConfigQuery, jobConfigQueryString, jobType, @@ -117,7 +121,8 @@ export const ConfigurationStepForm: FC = ({ dependentVariableEmpty || jobType === undefined || maxDistinctValuesError !== undefined || - requiredFieldsError !== undefined; + requiredFieldsError !== undefined || + unsupportedFieldsError !== undefined; const loadDepVarOptions = async (formState: State['form']) => { setLoadingDepVarOptions(true); @@ -187,7 +192,8 @@ export const ConfigurationStepForm: FC = ({ setLoadingFieldOptions(false); setFieldOptionsFetchFail(false); setMaxDistinctValuesError(undefined); - setExcludesTableItems(fieldSelection ? fieldSelection : []); + setUnsupportedFieldsError(undefined); + setIncludesTableItems(fieldSelection ? fieldSelection : []); setFormState({ ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemory } : {}), requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined, @@ -200,6 +206,7 @@ export const ConfigurationStepForm: FC = ({ } } else { let maxDistinctValuesErrorMessage; + let unsupportedFieldsErrorMessage; if ( jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && errorMessage.includes('status_exception') && @@ -208,6 +215,10 @@ export const ConfigurationStepForm: FC = ({ maxDistinctValuesErrorMessage = errorMessage; } + if (errorMessage.includes('status_exception') && errorMessage.includes('unsupported type')) { + unsupportedFieldsErrorMessage = errorMessage; + } + if ( errorMessage.includes('status_exception') && errorMessage.includes('Unable to estimate memory usage as no documents') @@ -231,6 +242,7 @@ export const ConfigurationStepForm: FC = ({ setLoadingFieldOptions(false); setFieldOptionsFetchFail(true); setMaxDistinctValuesError(maxDistinctValuesErrorMessage); + setUnsupportedFieldsError(unsupportedFieldsErrorMessage); setFormState({ ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}), }); @@ -267,7 +279,7 @@ export const ConfigurationStepForm: FC = ({ return () => { debouncedGetExplainData.cancel(); }; - }, [jobType, dependentVariable, trainingPercent, JSON.stringify(excludes), jobConfigQueryString]); + }, [jobType, dependentVariable, trainingPercent, JSON.stringify(includes), jobConfigQueryString]); return ( @@ -392,21 +404,32 @@ export const ConfigurationStepForm: FC = ({ )} - diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx index f31c9cd28f65..da547ee6255a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx @@ -71,7 +71,7 @@ export const JobType: FC = ({ type, setFormState }) => { setFormState({ previousJobType: type, jobType: value, - excludes: [], + includes: [], requiredFieldsError: undefined, }); }} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx index 0d1690cf1794..8ad49b84134c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useState } from 'react'; +import React, { FC, useState } from 'react'; import { EuiButton, EuiCheckbox, @@ -45,7 +45,7 @@ export const CreateStep: FC = ({ actions, state, step }) => { }; return ( - +
{!isJobCreated && !isJobStarted && ( @@ -88,6 +88,6 @@ export const CreateStep: FC = ({ actions, state, step }) => { {isJobCreated === true && showProgress && } {isJobCreated === true && } - +
); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx index a40813ed2fc3..2e027b7b67e5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx @@ -19,14 +19,19 @@ export const DetailsStep: FC = ({ step, stepActivated, }) => { + const showForm = step === ANALYTICS_STEPS.DETAILS; + const showDetails = step !== ANALYTICS_STEPS.DETAILS && stepActivated === true; + + const dataTestSubj = `mlAnalyticsCreateJobWizardDetailsStep${showForm ? ' active' : ''}${ + showDetails ? ' summary' : '' + }`; + return ( - - {step === ANALYTICS_STEPS.DETAILS && ( + + {showForm && ( )} - {step !== ANALYTICS_STEPS.DETAILS && stepActivated === true && ( - - )} + {showDetails && } ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx index e82142889004..04dd25896d44 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx @@ -109,7 +109,6 @@ export const Page: FC = ({ jobId }) => { /> ), status: currentStep >= ANALYTICS_STEPS.ADVANCED ? undefined : ('incomplete' as EuiStepStatus), - 'data-test-subj': 'mlAnalyticsCreateJobWizardAdvancedStep', }, { title: i18n.translate('xpack.ml.dataframe.analytics.creation.detailsStepTitle', { @@ -124,7 +123,6 @@ export const Page: FC = ({ jobId }) => { /> ), status: currentStep >= ANALYTICS_STEPS.DETAILS ? undefined : ('incomplete' as EuiStepStatus), - 'data-test-subj': 'mlAnalyticsCreateJobWizardDetailsStep', }, { title: i18n.translate('xpack.ml.dataframe.analytics.creation.createStepTitle', { @@ -132,7 +130,6 @@ export const Page: FC = ({ jobId }) => { }), children: , status: currentStep >= ANALYTICS_STEPS.CREATE ? undefined : ('incomplete' as EuiStepStatus), - 'data-test-subj': 'mlAnalyticsCreateJobWizardCreateStep', }, ]; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts index 01d92d8e192c..4227c19fec5a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts @@ -64,7 +64,7 @@ describe('Analytics job clone action', () => { }, analyzed_fields: { includes: [], - excludes: ['id', 'outlier'], + excludes: [], }, model_memory_limit: '1mb', allow_lazy_start: false, @@ -96,7 +96,7 @@ describe('Analytics job clone action', () => { }, }, analyzed_fields: { - includes: [], + includes: ['included_field', 'other_included_field'], excludes: [], }, model_memory_limit: '150mb', @@ -140,6 +140,40 @@ describe('Analytics job clone action', () => { expect(isAdvancedConfig(advancedClassificationJob)).toBe(true); }); + test('should detect advanced classification job with excludes set', () => { + const advancedClassificationJob = { + description: "Classification job with 'bank-marketing' dataset", + source: { + index: ['bank-marketing'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'dest_bank_1', + results_field: 'ml', + }, + analysis: { + classification: { + dependent_variable: 'y', + num_top_classes: 2, + num_top_feature_importance_values: 4, + prediction_field_name: 'y_prediction', + training_percent: 2, + randomize_seed: 6233212276062807000, + }, + }, + analyzed_fields: { + includes: [], + excludes: ['excluded_field', 'other_excluded_field'], + }, + model_memory_limit: '350mb', + allow_lazy_start: false, + }; + + expect(isAdvancedConfig(advancedClassificationJob)).toBe(true); + }); + test('should detect advanced regression job', () => { const advancedRegressionJob = { description: "Outlier detection job with 'glass' dataset", @@ -161,7 +195,7 @@ describe('Analytics job clone action', () => { }, analyzed_fields: { includes: [], - excludes: ['id', 'outlier'], + excludes: [], }, model_memory_limit: '1mb', allow_lazy_start: false, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx index f184c7c5d874..bff54bc28329 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx @@ -217,11 +217,11 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo analyzed_fields: { excludes: { optional: true, - formKey: 'excludes', defaultValue: [], }, includes: { optional: true, + formKey: 'includes', defaultValue: [], }, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 8bace7b4f595..81d35679443b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -116,7 +116,7 @@ export const validateNumTopFeatureImportanceValues = ( }; export const validateAdvancedEditor = (state: State): State => { - const { jobIdEmpty, jobIdValid, jobIdExists, jobType, createIndexPattern, excludes } = state.form; + const { jobIdEmpty, jobIdValid, jobIdExists, jobType, createIndexPattern, includes } = state.form; const { jobConfig } = state; state.advancedEditorMessages = []; @@ -152,7 +152,7 @@ export const validateAdvancedEditor = (state: State): State => { } let dependentVariableEmpty = false; - let excludesValid = true; + let includesValid = true; let trainingPercentValid = true; let numTopFeatureImportanceValuesValid = true; @@ -170,14 +170,19 @@ export const validateAdvancedEditor = (state: State): State => { const dependentVariableName = getDependentVar(jobConfig.analysis) || ''; dependentVariableEmpty = dependentVariableName === ''; - if (!dependentVariableEmpty && excludes.includes(dependentVariableName)) { - excludesValid = false; + if ( + !dependentVariableEmpty && + includes !== undefined && + includes.length > 0 && + !includes.includes(dependentVariableName) + ) { + includesValid = false; state.advancedEditorMessages.push({ error: i18n.translate( - 'xpack.ml.dataframe.analytics.create.advancedEditorMessage.excludesInvalid', + 'xpack.ml.dataframe.analytics.create.advancedEditorMessage.includesInvalid', { - defaultMessage: 'The dependent variable cannot be excluded.', + defaultMessage: 'The dependent variable must be included.', } ), message: '', @@ -321,7 +326,7 @@ export const validateAdvancedEditor = (state: State): State => { state.form.destinationIndexPatternTitleExists = destinationIndexPatternTitleExists; state.isValid = - excludesValid && + includesValid && trainingPercentValid && state.form.modelMemoryLimitUnitValid && !jobIdEmpty && diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index b9a9caadcebd..d397dfc315da 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -42,6 +42,37 @@ const regJobConfig = { allow_lazy_start: false, }; +const outlierJobConfig = { + id: 'outlier-test-01', + description: 'outlier test job description', + source: { + index: ['outlier-test-index'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'outlier-test-01-index', + results_field: 'ml', + }, + analysis: { + outlier_detection: { + feature_influence_threshold: 0.01, + outlier_fraction: 0.05, + compute_feature_influence: false, + method: 'lof', + }, + }, + analyzed_fields: { + includes: ['field', 'other_field'], + excludes: [], + }, + model_memory_limit: '22mb', + create_time: 1590514291395, + version: '8.0.0', + allow_lazy_start: false, +}; + describe('useCreateAnalyticsForm', () => { test('state: getJobConfigFromFormState()', () => { const state = getInitialState(); @@ -53,8 +84,8 @@ describe('useCreateAnalyticsForm', () => { expect(jobConfig?.dest?.index).toBe('the-destination-index'); expect(jobConfig?.source?.index).toBe('the-source-index'); - expect(jobConfig?.analyzed_fields?.excludes).toStrictEqual([]); - expect(typeof jobConfig?.analyzed_fields?.includes).toBe('undefined'); + expect(jobConfig?.analyzed_fields?.includes).toStrictEqual([]); + expect(typeof jobConfig?.analyzed_fields?.excludes).toBe('undefined'); // test the conversion of comma-separated Kibana index patterns to ES array based index patterns state.form.sourceIndex = 'the-source-index-1,the-source-index-2'; @@ -65,11 +96,11 @@ describe('useCreateAnalyticsForm', () => { ]); }); - test('state: getCloneFormStateFromJobConfig()', () => { + test('state: getCloneFormStateFromJobConfig() regression', () => { const clonedState = getCloneFormStateFromJobConfig(regJobConfig); expect(clonedState?.sourceIndex).toBe('reg-test-index'); - expect(clonedState?.excludes).toStrictEqual([]); + expect(clonedState?.includes).toStrictEqual([]); expect(clonedState?.dependentVariable).toBe('price'); expect(clonedState?.numTopFeatureImportanceValues).toBe(2); expect(clonedState?.predictionFieldName).toBe('airbnb_test'); @@ -80,4 +111,19 @@ describe('useCreateAnalyticsForm', () => { expect(clonedState?.destinationIndex).toBe(undefined); expect(clonedState?.jobId).toBe(undefined); }); + + test('state: getCloneFormStateFromJobConfig() outlier detection', () => { + const clonedState = getCloneFormStateFromJobConfig(outlierJobConfig); + + expect(clonedState?.sourceIndex).toBe('outlier-test-index'); + expect(clonedState?.includes).toStrictEqual(['field', 'other_field']); + expect(clonedState?.featureInfluenceThreshold).toBe(0.01); + expect(clonedState?.outlierFraction).toBe(0.05); + expect(clonedState?.computeFeatureInfluence).toBe(false); + expect(clonedState?.method).toBe('lof'); + expect(clonedState?.modelMemoryLimit).toBe('22mb'); + // destination index and job id should be undefined + expect(clonedState?.destinationIndex).toBe(undefined); + expect(clonedState?.jobId).toBe(undefined); + }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 241866b56c5c..da6e2e440a26 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -7,11 +7,8 @@ import { DeepPartial, DeepReadonly } from '../../../../../../../common/types/common'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; import { mlNodesAvailable } from '../../../../../ml_nodes_check'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { - isClassificationAnalysis, - isRegressionAnalysis, DataFrameAnalyticsId, DataFrameAnalyticsConfig, ANALYSIS_CONFIG_TYPE, @@ -57,10 +54,10 @@ export interface State { destinationIndexNameValid: boolean; destinationIndexPatternTitleExists: boolean; eta: undefined | number; - excludes: string[]; featureBagFraction: undefined | number; featureInfluenceThreshold: undefined | number; gamma: undefined | number; + includes: string[]; jobId: DataFrameAnalyticsId; jobIdExists: boolean; jobIdEmpty: boolean; @@ -122,10 +119,10 @@ export const getInitialState = (): State => ({ destinationIndexNameValid: false, destinationIndexPatternTitleExists: false, eta: undefined, - excludes: [], featureBagFraction: undefined, featureInfluenceThreshold: undefined, gamma: undefined, + includes: [], jobId: '', jobIdExists: false, jobIdEmpty: true, @@ -175,55 +172,6 @@ export const getInitialState = (): State => ({ estimatedModelMemoryLimit: '', }); -const getExcludesFields = (excluded: string[]) => { - const { fields } = newJobCapsService; - const updatedExcluded: string[] = []; - // Loop through excluded fields to check for multiple types of same field - for (let i = 0; i < excluded.length; i++) { - const fieldName = excluded[i]; - let mainField; - - // No dot in fieldName - it is the main field - if (fieldName.includes('.') === false) { - mainField = fieldName; - } else { - // Dot in fieldName - check if there's a field whose name equals the fieldName with the last dot suffix removed - const regex = /\.[^.]*$/; - const suffixRemovedField = fieldName.replace(regex, ''); - const fieldMatch = newJobCapsService.getFieldById(suffixRemovedField); - - // There's a match - set as the main field - if (fieldMatch !== null) { - mainField = suffixRemovedField; - } else { - // No main field to be found - add the fieldName to updatedExcluded array if it's not already there - if (updatedExcluded.includes(fieldName) === false) { - updatedExcluded.push(fieldName); - } - } - } - - if (mainField !== undefined) { - // Add the main field to the updatedExcluded array if it's not already there - if (updatedExcluded.includes(mainField) === false) { - updatedExcluded.push(mainField); - } - // Create regex to find all other fields whose names begin with main field followed by a dot - const regex = new RegExp(`${mainField}\\..+`); - - // Loop through fields and add fields matching the pattern to updatedExcluded array - for (let j = 0; j < fields.length; j++) { - const field = fields[j].name; - if (updatedExcluded.includes(field) === false && field.match(regex) !== null) { - updatedExcluded.push(field); - } - } - } - } - - return updatedExcluded; -}; - export const getJobConfigFromFormState = ( formState: State['form'] ): DeepPartial => { @@ -242,7 +190,7 @@ export const getJobConfigFromFormState = ( index: formState.destinationIndex, }, analyzed_fields: { - excludes: getExcludesFields(formState.excludes), + includes: formState.includes, }, analysis: { outlier_detection: {}, @@ -333,21 +281,16 @@ export function getCloneFormStateFromJobConfig( ? analyticsJobConfig.source.index.join(',') : analyticsJobConfig.source.index, modelMemoryLimit: analyticsJobConfig.model_memory_limit, - excludes: analyticsJobConfig.analyzed_fields.excludes, + includes: analyticsJobConfig.analyzed_fields.includes, }; - if ( - isRegressionAnalysis(analyticsJobConfig.analysis) || - isClassificationAnalysis(analyticsJobConfig.analysis) - ) { - const analysisConfig = analyticsJobConfig.analysis[jobType]; + const analysisConfig = analyticsJobConfig.analysis[jobType]; - for (const key in analysisConfig) { - if (analysisConfig.hasOwnProperty(key)) { - const camelCased = toCamelCase(key); - // @ts-ignore - resultState[camelCased] = analysisConfig[key]; - } + for (const key in analysisConfig) { + if (analysisConfig.hasOwnProperty(key)) { + const camelCased = toCamelCase(key); + // @ts-ignore + resultState[camelCased] = analysisConfig[key]; } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 51c6b33579f5..d6f9cd383ae9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9564,7 +9564,6 @@ "xpack.ml.dataframe.analytics.create.advancedEditorMessage.destinationIndexNameEmpty": "デスティネーションインデックス名は未入力のままにできません。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.destinationIndexNameExistsWarn": "この対象インデックス名のインデックスは既に存在します。この分析ジョブを実行すると、デスティネーションインデックスが変更されます。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.destinationIndexNameValid": "無効なデスティネーションインデックス名。", - "xpack.ml.dataframe.analytics.create.advancedEditorMessage.excludesInvalid": "従属変数を除外できません。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.modelMemoryLimitEmpty": "モデルメモリー制限フィールドを空にすることはできません。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.numTopFeatureImportanceValuesInvalid": "num_top_feature_importance_valuesの値は整数の{min}以上でなければなりません。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.sourceIndexNameEmpty": "ソースインデックス名は未入力のままにできません。", @@ -9591,7 +9590,6 @@ "xpack.ml.dataframe.analytics.create.errorGettingDataFrameIndexNames": "既存のインデックス名の取得中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles": "既存のインデックスパターンのタイトルの取得中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob": "データフレーム分析ジョブの開始中にエラーが発生しました。", - "xpack.ml.dataframe.analytics.create.excludedFieldsLabel": "除外されたフィールド", "xpack.ml.dataframe.analytics.create.indexPatternAlreadyExistsError": "このタイトルのインデックスパターンが既に存在します。", "xpack.ml.dataframe.analytics.create.indexPatternExistsError": "このタイトルのインデックスパターンが既に存在します。", "xpack.ml.dataframe.analytics.create.jobDescription.helpText": "オプションの説明テキストです", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8121df6d0509..235f8203608d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9568,7 +9568,6 @@ "xpack.ml.dataframe.analytics.create.advancedEditorMessage.destinationIndexNameEmpty": "目标索引名称不得为空。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.destinationIndexNameExistsWarn": "具有此目标索引名称的索引已存在。请注意,运行此分析作业将会修改此目标索引。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.destinationIndexNameValid": "目标索引名称无效。", - "xpack.ml.dataframe.analytics.create.advancedEditorMessage.excludesInvalid": "无法排除依赖变量。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.modelMemoryLimitEmpty": "模型内存限制字段不得为空。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.numTopFeatureImportanceValuesInvalid": "num_top_feature_importance_values 的值必须是 {min} 或更高的整数。", "xpack.ml.dataframe.analytics.create.advancedEditorMessage.sourceIndexNameEmpty": "源索引名称不得为空。", @@ -9595,7 +9594,6 @@ "xpack.ml.dataframe.analytics.create.errorGettingDataFrameIndexNames": "获取现有索引名称时发生错误:", "xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles": "获取现有索引模式标题时发生错误:", "xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob": "启动数据帧分析作业时发生错误:", - "xpack.ml.dataframe.analytics.create.excludedFieldsLabel": "排除的字段", "xpack.ml.dataframe.analytics.create.indexPatternAlreadyExistsError": "具有此名称的索引模式已存在。", "xpack.ml.dataframe.analytics.create.indexPatternExistsError": "具有此名称的索引模式已存在。", "xpack.ml.dataframe.analytics.create.jobDescription.helpText": "可选的描述文本", diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index 63b4ad3a8668..4a79610cadbd 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -66,6 +66,7 @@ export default function ({ getService }: FtrProviderContext) { it('selects the source data and loads the job wizard page', async () => { await ml.jobSourceSelection.selectSourceForAnalyticsJob(testData.source); + await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); it('selects the job type', async () => { @@ -83,6 +84,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.setTrainingPercent(testData.trainingPercent); }); + it('displays the source data preview', async () => { + await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists(); + }); + + it('displays the include fields selection', async () => { + await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); + }); + it('continues to the additional options step', async () => { await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts index 525e25d0158b..068ef48b095e 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts @@ -45,6 +45,7 @@ export default function ({ getService }: FtrProviderContext) { }, analysis: { classification: { + prediction_field_name: 'test', dependent_variable: 'y', training_percent: 20, }, @@ -107,6 +108,7 @@ export default function ({ getService }: FtrProviderContext) { }, analysis: { regression: { + prediction_field_name: 'test', dependent_variable: 'stab', training_percent: 20, }, @@ -157,9 +159,9 @@ export default function ({ getService }: FtrProviderContext) { }); it('should open the wizard with a proper header', async () => { - expect(await ml.dataFrameAnalyticsCreation.getHeaderText()).to.match( - /Clone analytics job/ - ); + const headerText = await ml.dataFrameAnalyticsCreation.getHeaderText(); + expect(headerText).to.match(/Clone job/); + await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); it('should have correct init form values for config step', async () => { @@ -174,7 +176,7 @@ export default function ({ getService }: FtrProviderContext) { it('should have correct init form values for additional options step', async () => { await ml.dataFrameAnalyticsCreation.assertInitialCloneJobAdditionalOptionsStep( - testData.job as DataFrameAnalyticsConfig + testData.job.analysis as DataFrameAnalyticsConfig['analysis'] ); }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts index cff59fa42abb..0202c8431ce3 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/index.ts @@ -12,6 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./outlier_detection_creation')); loadTestFile(require.resolve('./regression_creation')); loadTestFile(require.resolve('./classification_creation')); - // loadTestFile(require.resolve('./cloning')); + loadTestFile(require.resolve('./cloning')); }); } diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index 582b19f5e18a..500825f7d9d3 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -64,6 +64,7 @@ export default function ({ getService }: FtrProviderContext) { it('selects the source data and loads the job wizard page', async () => { await ml.jobSourceSelection.selectSourceForAnalyticsJob(testData.source); + await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); it('selects the job type', async () => { @@ -79,6 +80,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertTrainingPercentInputMissing(); }); + it('displays the source data preview', async () => { + await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists(); + }); + + it('displays the include fields selection', async () => { + await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); + }); + it('continues to the additional options step', async () => { await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index c8a6e1c96c21..33f0ee9cd99a 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -66,6 +66,7 @@ export default function ({ getService }: FtrProviderContext) { it('selects the source data and loads the job wizard page', async () => { await ml.jobSourceSelection.selectSourceForAnalyticsJob(testData.source); + await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); it('selects the job type', async () => { @@ -83,6 +84,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.setTrainingPercent(testData.trainingPercent); }); + it('displays the source data preview', async () => { + await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists(); + }); + + it('displays the include fields selection', async () => { + await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); + }); + it('continues to the additional options step', async () => { await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); }); diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index f67ea583e25c..918c982de02e 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -124,13 +124,21 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( await this.assertJobDescriptionValue(jobDescription); }, - // async assertExcludedFieldsSelection(expectedSelection: string[]) { - // const actualSelection = await comboBox.getComboBoxSelectedOptions( - // 'mlAnalyticsCreateJobWizardExcludesSelect' - // ); + async assertSourceDataPreviewExists() { + await testSubjects.existOrFail('mlAnalyticsCreationDataGrid loaded', { timeout: 5000 }); + }, + + async assertIncludeFieldsSelectionExists() { + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardIncludesSelect', { timeout: 5000 }); + }, + + // async assertIncludedFieldsSelection(expectedSelection: string[]) { + // const includesTable = await testSubjects.find('mlAnalyticsCreateJobWizardIncludesSelect'); + // const actualSelection = await includesTable.findByClassName('euiTableRow-isSelected'); + // expect(actualSelection).to.eql( // expectedSelection, - // `Excluded fields should be '${expectedSelection}' (got '${actualSelection}')` + // `Included fields should be '${expectedSelection}' (got '${actualSelection}')` // ); // }, @@ -252,19 +260,35 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( await this.assertTrainingPercentValue(trainingPercent); }, + async assertConfigurationStepActive() { + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardConfigurationStep active'); + }, + + async assertAdditionalOptionsStepActive() { + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardAdvancedStep active'); + }, + + async assertDetailsStepActive() { + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardDetailsStep active'); + }, + + async assertCreateStepActive() { + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardCreateStep active'); + }, + async continueToAdditionalOptionsStep() { - await testSubjects.click('mlAnalyticsCreateJobWizardContinueButton'); - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardAdvancedStep'); + await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await this.assertAdditionalOptionsStepActive(); }, async continueToDetailsStep() { - await testSubjects.click('mlAnalyticsCreateJobWizardContinueButton'); - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardDetailsStep'); + await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await this.assertDetailsStepActive(); }, async continueToCreateStep() { - await testSubjects.click('mlAnalyticsCreateJobWizardContinueButton'); - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardCreateStep'); + await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await this.assertCreateStepActive(); }, async assertModelMemoryInputExists() { @@ -282,6 +306,17 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( ); }, + async assertPredictionFieldNameValue(expectedValue: string) { + const actualPredictedFieldName = await testSubjects.getAttribute( + 'mlAnalyticsCreateJobWizardPredictionFieldNameInput', + 'value' + ); + expect(actualPredictedFieldName).to.eql( + expectedValue, + `Prediction field name should be '${expectedValue}' (got '${actualPredictedFieldName}')` + ); + }, + async setModelMemory(modelMemory: string) { await retry.tryForTime(15 * 1000, async () => { await mlCommon.setValueWithChecks( @@ -372,11 +407,19 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( await this.assertDependentVariableSelection([job.analysis[jobType].dependent_variable]); await this.assertTrainingPercentValue(String(job.analysis[jobType].training_percent)); } - // await this.assertExcludedFieldsSelection(job.analyzed_fields.excludes); + await this.assertSourceDataPreviewExists(); + await this.assertIncludeFieldsSelectionExists(); + // await this.assertIncludedFieldsSelection(job.analyzed_fields.includes); }, - async assertInitialCloneJobAdditionalOptionsStep(job: DataFrameAnalyticsConfig) { - await this.assertModelMemoryValue(job.model_memory_limit); + async assertInitialCloneJobAdditionalOptionsStep( + analysis: DataFrameAnalyticsConfig['analysis'] + ) { + const jobType = Object.keys(analysis)[0]; + if (isClassificationAnalysis(analysis) || isRegressionAnalysis(analysis)) { + // @ts-ignore + await this.assertPredictionFieldNameValue(analysis[jobType].prediction_field_name); + } }, async assertInitialCloneJobDetailsStep(job: DataFrameAnalyticsConfig) { From 93bae2284ce5b9ce108230f34bf9b66dc8a06cc3 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 6 Jul 2020 13:23:28 -0400 Subject: [PATCH 07/21] [ML] DF Analytics: adds prompt for destination index pattern creation (#70651) * add warning if create index not selected * create indexPrompt component and set needsDestIndexPattern * translation for prompt text and link * create indexPattern text to warning color --- .../common/use_results_view_config.ts | 3 ++ .../details_step/details_step_form.tsx | 43 +++++++++++++---- .../exploration_page_wrapper.tsx | 4 +- .../exploration_results_table.tsx | 12 ++++- .../components/index_pattern_prompt/index.ts | 7 +++ .../index_pattern_prompt.tsx | 48 +++++++++++++++++++ .../outlier_exploration.tsx | 6 ++- 7 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts index 2570dd20416b..fde1b2610650 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -24,6 +24,7 @@ export const useResultsViewConfig = (jobId: string) => { const mlContext = useMlContext(); const [indexPattern, setIndexPattern] = useState(undefined); const [isInitialized, setIsInitialized] = useState(false); + const [needsDestIndexPattern, setNeedsDestIndexPattern] = useState(false); const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false); const [jobConfig, setJobConfig] = useState(undefined); const [jobCapsServiceErrorMessage, setJobCapsServiceErrorMessage] = useState( @@ -68,6 +69,7 @@ export const useResultsViewConfig = (jobId: string) => { } if (indexP === undefined) { + setNeedsDestIndexPattern(true); const sourceIndex = jobConfigUpdate.source.index[0]; const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); @@ -100,5 +102,6 @@ export const useResultsViewConfig = (jobId: string) => { jobConfig, jobConfigErrorMessage, jobStatus, + needsDestIndexPattern, }; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx index 67f8472e7ad1..d846ae95c2c7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx @@ -5,7 +5,15 @@ */ import React, { FC, Fragment, useRef } from 'react'; -import { EuiFieldText, EuiFormRow, EuiLink, EuiSpacer, EuiSwitch, EuiTextArea } from '@elastic/eui'; +import { + EuiFieldText, + EuiFormRow, + EuiLink, + EuiSpacer, + EuiSwitch, + EuiText, + EuiTextArea, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useMlKibana } from '../../../../../contexts/kibana'; @@ -188,15 +196,32 @@ export const DetailsStepForm: FC = ({ /> + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.shouldCreateIndexPatternMessage', + { + defaultMessage: + 'You may not be able to view job results if an index pattern is not created for the destination index.', + } + )} + , + ] + : []), + ]} > = ({ jobId, title, EvaluatePanel jobConfig, jobConfigErrorMessage, jobStatus, + needsDestIndexPattern, } = useResultsViewConfig(jobId); const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); @@ -64,9 +65,10 @@ export const ExplorationPageWrapper: FC = ({ jobId, title, EvaluatePanel indexPattern !== undefined && isInitialized === true && ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index 941fbefd7808..755bac699ce4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -33,6 +33,7 @@ import { getTaskStateBadge } from '../../../analytics_management/components/anal import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; import { ExplorationTitle } from '../exploration_title'; import { ExplorationQueryBar } from '../exploration_query_bar'; +import { IndexPatternPrompt } from '../index_pattern_prompt'; import { useExplorationResults } from './use_exploration_results'; @@ -55,12 +56,20 @@ interface Props { indexPattern: IndexPattern; jobConfig: DataFrameAnalyticsConfig; jobStatus?: DATA_FRAME_TASK_STATE; + needsDestIndexPattern: boolean; setEvaluateSearchQuery: React.Dispatch>; title: string; } export const ExplorationResultsTable: FC = React.memo( - ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery, title }) => { + ({ + indexPattern, + jobConfig, + jobStatus, + needsDestIndexPattern, + setEvaluateSearchQuery, + title, + }) => { const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); useEffect(() => { @@ -119,6 +128,7 @@ export const ExplorationResultsTable: FC = React.memo( id="mlDataFrameAnalyticsTableResultsPanel" data-test-subj="mlDFAnalyticsExplorationTablePanel" > + {needsDestIndexPattern && } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index.ts new file mode 100644 index 000000000000..0b012794c942 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/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 { IndexPatternPrompt } from './index_pattern_prompt'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx new file mode 100644 index 000000000000..f478dc639da2 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.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, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { useMlKibana } from '../../../../../contexts/kibana'; + +interface Props { + destIndex: string; +} + +export const IndexPatternPrompt: FC = ({ destIndex }) => { + const { + services: { + http: { basePath }, + }, + } = useMlKibana(); + + return ( + <> + + + + + ), + }} + /> + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 0b29b7f43bfc..9afb50c11fad 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -33,6 +33,7 @@ import { getTaskStateBadge } from '../../../analytics_management/components/anal import { ExplorationQueryBar } from '../exploration_query_bar'; import { ExplorationTitle } from '../exploration_title'; +import { IndexPatternPrompt } from '../index_pattern_prompt'; import { getFeatureCount } from './common'; import { useOutlierData } from './use_outlier_data'; @@ -49,7 +50,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = values: { jobId }, }); - const { indexPattern, jobConfig, jobStatus } = useResultsViewConfig(jobId); + const { indexPattern, jobConfig, jobStatus, needsDestIndexPattern } = useResultsViewConfig(jobId); const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery); @@ -82,6 +83,9 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = return ( + {jobConfig !== undefined && needsDestIndexPattern && ( + + )} Date: Mon, 6 Jul 2020 10:36:44 -0700 Subject: [PATCH 08/21] docs: add annotation user docs (#70265) --- docs/apm/api.asciidoc | 1 + docs/apm/apm-app-users.asciidoc | 50 ++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 54159b642dd1..2fbeea0534fc 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -355,6 +355,7 @@ allowing you to easily see how these events are impacting the performance of you By default, annotations are stored in a newly created `observability-annotations` index. The name of this index can be changed in your `config.yml` by editing `xpack.observability.annotations.index`. +If you change the default index name, you'll also need to <> accordingly. The following APIs are available: diff --git a/docs/apm/apm-app-users.asciidoc b/docs/apm/apm-app-users.asciidoc index 442a07d27972..d766c866f87e 100644 --- a/docs/apm/apm-app-users.asciidoc +++ b/docs/apm/apm-app-users.asciidoc @@ -4,7 +4,7 @@ :beat_default_index_prefix: apm :beat_kib_app: APM app -:annotation_index: `observability-annotations` +:annotation_index: observability-annotations ++++ Users and privileges @@ -102,6 +102,54 @@ Here are two examples: *********************************** *********************************** //// +[role="xpack"] +[[apm-app-annotation-user-create]] +=== APM app annotation user + +++++ +Create an annotation user +++++ + +NOTE: By default, the `apm_user` built-in role provides access to Observability annotations. +You only need to create an annotation user if the default annotation index +defined in <> has been customized. + +[[apm-app-annotation-user]] +==== Annotation user + +View deployment annotations in the APM app. + +. Create a new role, named something like `annotation_user`, +and assign the following privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +|Index +|`read` on +\{ANNOTATION_INDEX\}+^1^ +|Read-only access to the observability annotation index + +|Index +|`view_index_metadata` on +\{ANNOTATION_INDEX\}+^1^ +|Read-only access to observability annotation index metadata +|==== ++ +^1^ +\{ANNOTATION_INDEX\}+ should be the index name you've defined in +<>. + +. Assign the `annotation_user` created previously, and the built-in roles necessary to create +a <> or <> APM reader to any users that need to view annotations in the APM app + +[[apm-app-annotation-api]] +==== Annotation API + +See <>. + +//// +*********************************** *********************************** +//// + [role="xpack"] [[apm-app-central-config-user]] === APM app central config user From 21af99c9b986fc5625471d9e862cb8addcbefb16 Mon Sep 17 00:00:00 2001 From: Octavio Ranieri <60898133+octavioranieri@users.noreply.github.com> Date: Mon, 6 Jul 2020 14:49:56 -0300 Subject: [PATCH 09/21] [Canvas] Fix falsey/null value bug for dropdown choices (#69290) * Fixed falsey/null value bug for dropdown choices * Filter only null and undefined values Co-authored-by: Elastic Machine --- .../canvas_plugin_src/functions/common/dropdownControl.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts index 7231f01671e0..74a9061b5df2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts @@ -52,8 +52,12 @@ export function dropdownControl(): ExpressionFunctionDefinition< fn: (input, { valueColumn, filterColumn, filterGroup }) => { let choices = []; - if (input.rows[0][valueColumn]) { - choices = uniq(input.rows.map((row) => row[valueColumn])).sort(); + const filteredRows = input.rows.filter( + (row) => row[valueColumn] !== null && row[valueColumn] !== undefined + ); + + if (filteredRows.length > 0) { + choices = uniq(filteredRows.map((row) => row[valueColumn])).sort(); } const column = filterColumn || valueColumn; From bd952721a4791da8cd7c8d9cfdfa1e6660cf9fa8 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:02:26 +0300 Subject: [PATCH 10/21] Convert tag cloud tests to jest (#70066) * Convert tag cloud tests to jest * Add mocks to test_utils and remove tests from legacy * Revert changes made by accident * Update tag_cloud_visualization.test.js * Update tag_cloud.test.js * Update jsdom_svg_mocks.ts * Add restoring previous value to window.SVGElement.prototype.transform * Get rid of some deep imports * Reimport jsdom_svg_mocks functions from test_utils/public * Get rid of ExprVis by inlining some of its params to vis object Co-authored-by: Elastic Machine Co-authored-by: Alexey Antonov --- package.json | 3 +- .../vis_type_tagcloud/afterparamchange.png | Bin 11622 -> 0 bytes .../vis_type_tagcloud/afterresize.png | Bin 9012 -> 0 bytes .../__tests__/vis_type_tagcloud/basicdraw.png | Bin 12964 -> 0 bytes .../vis_type_tagcloud/simpleload.png | Bin 10359 -> 0 bytes .../tag_cloud_visualization.js | 202 --------------- .../__snapshots__/tag_cloud.test.js.snap | 3 + .../tag_cloud_visualization.test.js.snap | 7 + .../public/components/tag_cloud.test.js} | 231 ++++++++---------- .../tag_cloud_visualization.test.js | 176 +++++++++++++ src/test_utils/public/helpers/index.ts | 2 + .../public/helpers/jsdom_svg_mocks.ts | 57 +++++ src/test_utils/public/index.ts | 20 ++ yarn.lock | 25 ++ 14 files changed, 395 insertions(+), 331 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/afterparamchange.png delete mode 100644 src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/afterresize.png delete mode 100644 src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/basicdraw.png delete mode 100644 src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/simpleload.png delete mode 100644 src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js create mode 100644 src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap create mode 100644 src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap rename src/{legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js => plugins/vis_type_tagcloud/public/components/tag_cloud.test.js} (72%) create mode 100644 src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js create mode 100644 src/test_utils/public/helpers/jsdom_svg_mocks.ts create mode 100644 src/test_utils/public/index.ts diff --git a/package.json b/package.json index 8e51f9207eaf..2f6b643b0260 100644 --- a/package.json +++ b/package.json @@ -455,9 +455,10 @@ "is-path-inside": "^2.1.0", "istanbul-instrumenter-loader": "3.0.1", "jest": "^25.5.4", - "jest-environment-jsdom-thirteen": "^1.0.1", + "jest-canvas-mock": "^2.2.0", "jest-circus": "^25.5.4", "jest-cli": "^25.5.4", + "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", "jimp": "^0.9.6", "json5": "^1.0.1", diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/afterparamchange.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/afterparamchange.png deleted file mode 100644 index bc41213edc7b60126bad788f5af94d995fa1a4cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11622 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 O8cW?999lx{14?i>yU_n~ok=k&Gcnr$v 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 diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/basicdraw.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/basicdraw.png deleted file mode 100644 index 3716867865e4430d4080d00f0c0e16b2b67ebc91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12964 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 diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/simpleload.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/simpleload.png deleted file mode 100644 index 6ea090562d46e774fcbd12682cccd36f39bb791f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10359 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 Z>~6C!wyQT20TvVjmn`iO73a|p{u5H(=8pgX diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js deleted file mode 100644 index 4a6e9e776521..000000000000 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud_visualization.js +++ /dev/null @@ -1,202 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { ImageComparator } from 'test_utils/image_comparator'; -import basicdrawPng from './basicdraw.png'; -import afterresizePng from './afterresize.png'; -import afterparamChange from './afterparamchange.png'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ExprVis } from '../../../../../../plugins/visualizations/public/expressions/vis'; - -// Replace with mock when converting to jest tests -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { seedColors } from '../../../../../../plugins/charts/public/services/colors/seed_colors'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BaseVisType } from '../../../../../../plugins/visualizations/public/vis_types/base_vis_type'; -// Will be replaced with new path when tests are moved -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { createTagCloudVisTypeDefinition } from '../../../../../../plugins/vis_type_tagcloud/public/tag_cloud_type'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { createTagCloudVisualization } from '../../../../../../plugins/vis_type_tagcloud/public/components/tag_cloud_visualization'; -import { npStart } from 'ui/new_platform'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setFormatService } from '../../../../../../plugins/vis_type_tagcloud/public/services'; - -const THRESHOLD = 0.65; -const PIXEL_DIFF = 64; -describe('TagCloudVisualizationTest', function () { - let domNode; - let vis; - let imageComparator; - - const dummyTableGroup = { - columns: [ - { - id: 'col-0', - title: 'geo.dest: Descending', - }, - { - id: 'col-1', - title: 'Count', - }, - ], - rows: [ - { 'col-0': 'CN', 'col-1': 26 }, - { 'col-0': 'IN', 'col-1': 17 }, - { 'col-0': 'US', 'col-1': 6 }, - { 'col-0': 'DE', 'col-1': 4 }, - { 'col-0': 'BR', 'col-1': 3 }, - ], - }; - const TagCloudVisualization = createTagCloudVisualization({ - colors: { - seedColors, - }, - }); - - before(() => setFormatService(npStart.plugins.data.fieldFormats)); - - beforeEach(ngMock.module('kibana')); - - describe('TagCloudVisualization - basics', function () { - beforeEach(async function () { - const visType = new BaseVisType(createTagCloudVisTypeDefinition({ colors: seedColors })); - setupDOM('512px', '512px'); - imageComparator = new ImageComparator(); - vis = new ExprVis({ - type: visType, - params: { - bucket: { accessor: 0, format: {} }, - metric: { accessor: 0, format: {} }, - }, - data: {}, - }); - }); - - afterEach(function () { - teardownDOM(); - imageComparator.destroy(); - }); - - it('simple draw', async function () { - const tagcloudVisualization = new TagCloudVisualization(domNode, vis); - - await tagcloudVisualization.render(dummyTableGroup, vis.params, { - resize: false, - params: true, - aggs: true, - data: true, - uiState: false, - }); - - const svgNode = domNode.querySelector('svg'); - const mismatchedPixels = await imageComparator.compareDOMContents( - svgNode.outerHTML, - 512, - 512, - basicdrawPng, - THRESHOLD - ); - expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); - }); - - it('with resize', async function () { - const tagcloudVisualization = new TagCloudVisualization(domNode, vis); - await tagcloudVisualization.render(dummyTableGroup, vis.params, { - resize: false, - params: true, - aggs: true, - data: true, - uiState: false, - }); - - domNode.style.width = '256px'; - domNode.style.height = '368px'; - await tagcloudVisualization.render(dummyTableGroup, vis.params, { - resize: true, - params: false, - aggs: false, - data: false, - uiState: false, - }); - - const svgNode = domNode.querySelector('svg'); - const mismatchedPixels = await imageComparator.compareDOMContents( - svgNode.outerHTML, - 256, - 368, - afterresizePng, - THRESHOLD - ); - expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); - }); - - it('with param change', async function () { - const tagcloudVisualization = new TagCloudVisualization(domNode, vis); - await tagcloudVisualization.render(dummyTableGroup, vis.params, { - resize: false, - params: true, - aggs: true, - data: true, - uiState: false, - }); - - domNode.style.width = '256px'; - domNode.style.height = '368px'; - vis.params.orientation = 'right angled'; - vis.params.minFontSize = 70; - await tagcloudVisualization.render(dummyTableGroup, vis.params, { - resize: true, - params: true, - aggs: false, - data: false, - uiState: false, - }); - - const svgNode = domNode.querySelector('svg'); - const mismatchedPixels = await imageComparator.compareDOMContents( - svgNode.outerHTML, - 256, - 368, - afterparamChange, - THRESHOLD - ); - expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); - }); - }); - - function setupDOM(width, height) { - domNode = document.createElement('div'); - domNode.style.top = '0'; - domNode.style.left = '0'; - domNode.style.width = width; - domNode.style.height = height; - domNode.style.position = 'fixed'; - domNode.style.border = '1px solid blue'; - domNode.style['pointer-events'] = 'none'; - document.body.appendChild(domNode); - } - - function teardownDOM() { - domNode.innerHTML = ''; - document.body.removeChild(domNode); - } -}); diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap new file mode 100644 index 000000000000..e32425a09542 --- /dev/null +++ b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`tag cloud tests tagcloudscreenshot should render simple image 1`] = `"foobarfoobar"`; diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap new file mode 100644 index 000000000000..dbc3dd1202cb --- /dev/null +++ b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TagCloudVisualizationTest TagCloudVisualization - basics simple draw 1`] = `"CNINUSDEBR"`; + +exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 1`] = `"CNINUSDEBR"`; + +exports[`TagCloudVisualizationTest TagCloudVisualization - basics with resize 1`] = `"CNINUSDEBR"`; diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js similarity index 72% rename from src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js rename to src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js index 35c7b77687b9..89a6a67bcb2f 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_tagcloud/tag_cloud.js +++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js @@ -17,22 +17,27 @@ * under the License. */ -import expect from '@kbn/expect'; import _ from 'lodash'; import d3 from 'd3'; +import 'jest-canvas-mock'; import { fromNode, delay } from 'bluebird'; -import { ImageComparator } from 'test_utils/image_comparator'; -import simpleloadPng from './simpleload.png'; +import { TagCloud } from './tag_cloud'; +import { setHTMLElementOffset, setSVGElementGetBBox } from '../../../../test_utils/public'; -// Replace with mock when converting to jest tests -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { seedColors } from '../../../../../../plugins/charts/public/services/colors/seed_colors'; -// Will be replaced with new path when tests are moved -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TagCloud } from '../../../../../../plugins/vis_type_tagcloud/public/components/tag_cloud'; +describe('tag cloud tests', () => { + let SVGElementGetBBoxSpyInstance; + let HTMLElementOffsetMockInstance; + + beforeEach(() => { + setupDOM(); + }); + + afterEach(() => { + SVGElementGetBBoxSpyInstance.mockRestore(); + HTMLElementOffsetMockInstance.mockRestore(); + }); -describe('tag cloud tests', function () { const minValue = 1; const maxValue = 9; const midValue = (minValue + maxValue) / 2; @@ -100,16 +105,15 @@ describe('tag cloud tests', function () { let domNode; let tagCloud; - const colorScale = d3.scale.ordinal().range(seedColors); + const colorScale = d3.scale + .ordinal() + .range(['#00a69b', '#57c17b', '#6f87d8', '#663db8', '#bc52bc', '#9e3533', '#daa05d']); function setupDOM() { domNode = document.createElement('div'); - domNode.style.top = '0'; - domNode.style.left = '0'; - domNode.style.width = '512px'; - domNode.style.height = '512px'; - domNode.style.position = 'fixed'; - domNode.style['pointer-events'] = 'none'; + SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(); + HTMLElementOffsetMockInstance = setHTMLElementOffset(512, 512); + document.body.appendChild(domNode); } @@ -126,42 +130,39 @@ describe('tag cloud tests', function () { sqrtScaleTest, biggerFontTest, trimDataTest, - ].forEach(function (test) { + ].forEach(function (currentTest) { describe(`should position elements correctly for options: ${JSON.stringify( - test.options - )}`, function () { - beforeEach(async function () { - setupDOM(); + currentTest.options + )}`, () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); - tagCloud.setData(test.data); - tagCloud.setOptions(test.options); + tagCloud.setData(currentTest.data); + tagCloud.setOptions(currentTest.options); await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); - it( + test( 'positions should be ok', - handleExpectedBlip(function () { + handleExpectedBlip(() => { const textElements = domNode.querySelectorAll('text'); - verifyTagProperties(test.expected, textElements, tagCloud); + verifyTagProperties(currentTest.expected, textElements, tagCloud); }) ); }); }); - [5, 100, 200, 300, 500].forEach(function (timeout) { - describe(`should only send single renderComplete event at the very end, using ${timeout}ms timeout`, function () { - beforeEach(async function () { - setupDOM(); - + [5, 100, 200, 300, 500].forEach((timeout) => { + describe(`should only send single renderComplete event at the very end, using ${timeout}ms timeout`, () => { + beforeEach(async () => { //TagCloud takes at least 600ms to complete (due to d3 animation) //renderComplete should only notify at the last one tagCloud = new TagCloud(domNode, colorScale); @@ -176,16 +177,16 @@ describe('tag cloud tests', function () { afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); - it( + test( 'positions should be ok', - handleExpectedBlip(function () { + handleExpectedBlip(() => { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(logScaleTest.expected, textElements, tagCloud); }) @@ -193,9 +194,8 @@ describe('tag cloud tests', function () { }); }); - describe('should use the latest state before notifying (when modifying options multiple times)', function () { - beforeEach(async function () { - setupDOM(); + describe('should use the latest state before notifying (when modifying options multiple times)', () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); @@ -205,53 +205,53 @@ describe('tag cloud tests', function () { afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); - it( + test( 'positions should be ok', - handleExpectedBlip(function () { + handleExpectedBlip(() => { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(logScaleTest.expected, textElements, tagCloud); }) ); }); - describe('should use the latest state before notifying (when modifying data multiple times)', function () { - beforeEach(async function () { - setupDOM(); + describe('should use the latest state before notifying (when modifying data multiple times)', () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); tagCloud.setData(trimDataTest.data); + await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); - it( + test( 'positions should be ok', - handleExpectedBlip(function () { + handleExpectedBlip(() => { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(trimDataTest.expected, textElements, tagCloud); }) ); }); - describe('should not get multiple render-events', function () { + describe('should not get multiple render-events', () => { let counter; - beforeEach(function () { + beforeEach(() => { counter = 0; - setupDOM(); + return new Promise((resolve, reject) => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); @@ -281,31 +281,32 @@ describe('tag cloud tests', function () { afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); - it( + test( 'positions should be ok', - handleExpectedBlip(function () { + handleExpectedBlip(() => { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(logScaleTest.expected, textElements, tagCloud); }) ); }); - describe('should show correct data when state-updates are interleaved with resize event', function () { - beforeEach(async function () { - setupDOM(); + describe('should show correct data when state-updates are interleaved with resize event', () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(logScaleTest.data); tagCloud.setOptions(logScaleTest.options); await delay(1000); //let layout run - domNode.style.width = '600px'; - domNode.style.height = '600px'; + + SVGElementGetBBoxSpyInstance.mockRestore(); + SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(600, 600); + tagCloud.resize(); //triggers new layout setTimeout(() => { //change the options at the very end too @@ -317,26 +318,23 @@ describe('tag cloud tests', function () { afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); - it( + test( 'positions should be ok', - handleExpectedBlip(function () { + handleExpectedBlip(() => { const textElements = domNode.querySelectorAll('text'); verifyTagProperties(baseTest.expected, textElements, tagCloud); }) ); }); - describe(`should not put elements in view when container is too small`, function () { - beforeEach(async function () { - setupDOM(); - domNode.style.width = '1px'; - domNode.style.height = '1px'; + describe(`should not put elements in view when container is too small`, () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); @@ -345,10 +343,10 @@ describe('tag cloud tests', function () { afterEach(teardownDOM); - it('completeness should not be ok', function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE); + test('completeness should not be ok', () => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.INCOMPLETE); }); - it('positions should not be ok', function () { + test('positions should not be ok', () => { const textElements = domNode.querySelectorAll('text'); for (let i = 0; i < textElements; i++) { const bbox = textElements[i].getBoundingClientRect(); @@ -357,96 +355,73 @@ describe('tag cloud tests', function () { }); }); - describe(`tags should fit after making container bigger`, function () { - beforeEach(async function () { - setupDOM(); - domNode.style.width = '1px'; - domNode.style.height = '1px'; - + describe(`tags should fit after making container bigger`, () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); await fromNode((cb) => tagCloud.once('renderComplete', cb)); //make bigger - domNode.style.width = '512px'; - domNode.style.height = '512px'; + tagCloud._size = [600, 600]; tagCloud.resize(); await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); - it( + test( 'completeness should be ok', - handleExpectedBlip(function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE); + handleExpectedBlip(() => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE); }) ); }); - describe(`tags should no longer fit after making container smaller`, function () { - beforeEach(async function () { - setupDOM(); + describe(`tags should no longer fit after making container smaller`, () => { + beforeEach(async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); await fromNode((cb) => tagCloud.once('renderComplete', cb)); //make smaller - domNode.style.width = '1px'; - domNode.style.height = '1px'; + tagCloud._size = []; tagCloud.resize(); await fromNode((cb) => tagCloud.once('renderComplete', cb)); }); afterEach(teardownDOM); - it('completeness should not be ok', function () { - expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE); + test('completeness should not be ok', () => { + expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.INCOMPLETE); }); }); - describe('tagcloudscreenshot', function () { - let imageComparator; - beforeEach(async function () { - setupDOM(); - imageComparator = new ImageComparator(); - }); + describe('tagcloudscreenshot', () => { + afterEach(teardownDOM); - afterEach(() => { - imageComparator.destroy(); - teardownDOM(); - }); - - it('should render simple image', async function () { + test('should render simple image', async () => { tagCloud = new TagCloud(domNode, colorScale); tagCloud.setData(baseTest.data); tagCloud.setOptions(baseTest.options); await fromNode((cb) => tagCloud.once('renderComplete', cb)); - const mismatchedPixels = await imageComparator.compareDOMContents( - domNode.innerHTML, - 512, - 512, - simpleloadPng, - 0.5 - ); - expect(mismatchedPixels).to.be.lessThan(64); + expect(domNode.innerHTML).toMatchSnapshot(); }); }); function verifyTagProperties(expectedValues, actualElements, tagCloud) { - expect(actualElements.length).to.equal(expectedValues.length); + expect(actualElements.length).toEqual(expectedValues.length); expectedValues.forEach((test, index) => { try { - expect(actualElements[index].style.fontSize).to.equal(test.fontSize); + expect(actualElements[index].style.fontSize).toEqual(test.fontSize); } catch (e) { throw new Error('fontsize is not correct: ' + e.message); } try { - expect(actualElements[index].innerHTML).to.equal(test.text); + expect(actualElements[index].innerHTML).toEqual(test.text); } catch (e) { throw new Error('fontsize is not correct: ' + e.message); } @@ -470,14 +445,14 @@ describe('tag cloud tests', function () { debugInfo: ${JSON.stringify(tagCloud.getDebugInfo())}`; try { - expect(bbox.top >= 0 && bbox.top <= domNode.offsetHeight).to.be(shouldBeInside); + expect(bbox.top >= 0 && bbox.top <= domNode.offsetHeight).toBe(shouldBeInside); } catch (e) { throw new Error( 'top boundary of tag should have been ' + (shouldBeInside ? 'inside' : 'outside') + message ); } try { - expect(bbox.bottom >= 0 && bbox.bottom <= domNode.offsetHeight).to.be(shouldBeInside); + expect(bbox.bottom >= 0 && bbox.bottom <= domNode.offsetHeight).toBe(shouldBeInside); } catch (e) { throw new Error( 'bottom boundary of tag should have been ' + @@ -486,14 +461,14 @@ describe('tag cloud tests', function () { ); } try { - expect(bbox.left >= 0 && bbox.left <= domNode.offsetWidth).to.be(shouldBeInside); + expect(bbox.left >= 0 && bbox.left <= domNode.offsetWidth).toBe(shouldBeInside); } catch (e) { throw new Error( 'left boundary of tag should have been ' + (shouldBeInside ? 'inside' : 'outside') + message ); } try { - expect(bbox.right >= 0 && bbox.right <= domNode.offsetWidth).to.be(shouldBeInside); + expect(bbox.right >= 0 && bbox.right <= domNode.offsetWidth).toBe(shouldBeInside); } catch (e) { throw new Error( 'right boundary of tag should have been ' + @@ -532,7 +507,7 @@ describe('tag cloud tests', function () { } function handleExpectedBlip(assertion) { - return function () { + return () => { if (!shouldAssert()) { return; } diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js new file mode 100644 index 000000000000..7f96066c1607 --- /dev/null +++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js @@ -0,0 +1,176 @@ +/* + * 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 'jest-canvas-mock'; + +import { createTagCloudVisTypeDefinition } from '../tag_cloud_type'; +import { createTagCloudVisualization } from './tag_cloud_visualization'; +import { setFormatService } from '../services'; +import { dataPluginMock } from '../../../data/public/mocks'; +import { setHTMLElementOffset, setSVGElementGetBBox } from '../../../../test_utils/public'; + +const seedColors = ['#00a69b', '#57c17b', '#6f87d8', '#663db8', '#bc52bc', '#9e3533', '#daa05d']; + +describe('TagCloudVisualizationTest', () => { + let domNode; + let vis; + let SVGElementGetBBoxSpyInstance; + let HTMLElementOffsetMockInstance; + + const dummyTableGroup = { + columns: [ + { + id: 'col-0', + title: 'geo.dest: Descending', + }, + { + id: 'col-1', + title: 'Count', + }, + ], + rows: [ + { 'col-0': 'CN', 'col-1': 26 }, + { 'col-0': 'IN', 'col-1': 17 }, + { 'col-0': 'US', 'col-1': 6 }, + { 'col-0': 'DE', 'col-1': 4 }, + { 'col-0': 'BR', 'col-1': 3 }, + ], + }; + const TagCloudVisualization = createTagCloudVisualization({ + colors: { + seedColors, + }, + }); + + const originTransformSVGElement = window.SVGElement.prototype.transform; + + beforeAll(() => { + setFormatService(dataPluginMock.createStartContract().fieldFormats); + Object.defineProperties(window.SVGElement.prototype, { + transform: { + get: () => ({ + baseVal: { + consolidate: () => {}, + }, + }), + configurable: true, + }, + }); + }); + + afterAll(() => { + SVGElementGetBBoxSpyInstance.mockRestore(); + HTMLElementOffsetMockInstance.mockRestore(); + window.SVGElement.prototype.transform = originTransformSVGElement; + }); + + describe('TagCloudVisualization - basics', () => { + beforeEach(async () => { + const visType = createTagCloudVisTypeDefinition({ colors: seedColors }); + setupDOM(512, 512); + + vis = { + type: visType, + params: { + bucket: { accessor: 0, format: {} }, + metric: { accessor: 0, format: {} }, + scale: 'linear', + orientation: 'single', + }, + data: {}, + }; + }); + + test('simple draw', async () => { + const tagcloudVisualization = new TagCloudVisualization(domNode, vis); + + await tagcloudVisualization.render(dummyTableGroup, vis.params, { + resize: false, + params: true, + aggs: true, + data: true, + uiState: false, + }); + + const svgNode = domNode.querySelector('svg'); + expect(svgNode.outerHTML).toMatchSnapshot(); + }); + + test('with resize', async () => { + const tagcloudVisualization = new TagCloudVisualization(domNode, vis); + await tagcloudVisualization.render(dummyTableGroup, vis.params, { + resize: false, + params: true, + aggs: true, + data: true, + uiState: false, + }); + + domNode.style.width = '256px'; + domNode.style.height = '368px'; + await tagcloudVisualization.render(dummyTableGroup, vis.params, { + resize: true, + params: false, + aggs: false, + data: false, + uiState: false, + }); + + const svgNode = domNode.querySelector('svg'); + expect(svgNode.outerHTML).toMatchSnapshot(); + }); + + test('with param change', async function () { + const tagcloudVisualization = new TagCloudVisualization(domNode, vis); + await tagcloudVisualization.render(dummyTableGroup, vis.params, { + resize: false, + params: true, + aggs: true, + data: true, + uiState: false, + }); + + SVGElementGetBBoxSpyInstance.mockRestore(); + SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(256, 368); + + HTMLElementOffsetMockInstance.mockRestore(); + HTMLElementOffsetMockInstance = setHTMLElementOffset(256, 386); + + vis.params.orientation = 'right angled'; + vis.params.minFontSize = 70; + await tagcloudVisualization.render(dummyTableGroup, vis.params, { + resize: true, + params: true, + aggs: false, + data: false, + uiState: false, + }); + + const svgNode = domNode.querySelector('svg'); + expect(svgNode.outerHTML).toMatchSnapshot(); + }); + }); + + function setupDOM(width, height) { + domNode = document.createElement('div'); + + HTMLElementOffsetMockInstance = setHTMLElementOffset(width, height); + SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(width, height); + } +}); diff --git a/src/test_utils/public/helpers/index.ts b/src/test_utils/public/helpers/index.ts index 79dc29e83bc3..c8447743ee28 100644 --- a/src/test_utils/public/helpers/index.ts +++ b/src/test_utils/public/helpers/index.ts @@ -24,3 +24,5 @@ export { WithStore } from './redux_helpers'; export { WithMemoryRouter, WithRoute, reactRouterMock } from './router_helpers'; export * from './utils'; + +export { setSVGElementGetBBox, setHTMLElementOffset } from './jsdom_svg_mocks'; diff --git a/src/test_utils/public/helpers/jsdom_svg_mocks.ts b/src/test_utils/public/helpers/jsdom_svg_mocks.ts new file mode 100644 index 000000000000..dbc8266f663f --- /dev/null +++ b/src/test_utils/public/helpers/jsdom_svg_mocks.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const setSVGElementGetBBox = ( + width: number, + height: number, + x: number = 0, + y: number = 0 +) => { + const SVGElementPrototype = SVGElement.prototype as any; + const originalGetBBox = SVGElementPrototype.getBBox; + + // getBBox is not in the SVGElement.prototype object by default, so we cannot use jest.spyOn for that case + SVGElementPrototype.getBBox = jest.fn(() => ({ + x, + y, + width, + height, + })); + + return { + mockRestore: () => { + SVGElementPrototype.getBBox = originalGetBBox; + }, + }; +}; + +export const setHTMLElementOffset = (width: number, height: number) => { + const offsetWidthSpy = jest.spyOn(window.HTMLElement.prototype, 'offsetWidth', 'get'); + offsetWidthSpy.mockReturnValue(width); + + const offsetHeightSpy = jest.spyOn(window.HTMLElement.prototype, 'offsetHeight', 'get'); + offsetHeightSpy.mockReturnValue(height); + + return { + mockRestore: () => { + offsetWidthSpy.mockRestore(); + offsetHeightSpy.mockRestore(); + }, + }; +}; diff --git a/src/test_utils/public/index.ts b/src/test_utils/public/index.ts new file mode 100644 index 000000000000..4f46dfe1578d --- /dev/null +++ b/src/test_utils/public/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 { setSVGElementGetBBox, setHTMLElementOffset } from './helpers'; diff --git a/yarn.lock b/yarn.lock index 7e4478038953..eb1943c5cd00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10379,6 +10379,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-convert@~0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" + integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" @@ -11444,6 +11449,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfontparser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" + integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= + csso@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" @@ -18922,6 +18932,14 @@ iterall@^1.2.2: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== +jest-canvas-mock@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz#45fbc58589c6ce9df50dc90bd8adce747cbdada7" + integrity sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw== + dependencies: + cssfontparser "^1.2.1" + parse-color "^1.0.0" + jest-changed-files@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c" @@ -23755,6 +23773,13 @@ parse-bmfont-xml@^1.1.4: xml-parse-from-string "^1.0.0" xml2js "^0.4.5" +parse-color@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" + integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= + dependencies: + color-convert "~0.5.0" + parse-entities@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.1.tgz#8112d88471319f27abae4d64964b122fe4e1b890" From 44925311fc8fe38bbed5269cbea6388d0263437e Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 6 Jul 2020 11:13:17 -0700 Subject: [PATCH 11/21] Fix kbn/optimizer tests (#70827) Co-authored-by: spalger --- .../basic_optimization.test.ts.snap | 66 +++++++++++++++++++ .../basic_optimization.test.ts | 14 ++-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 1466865df8d9..211cfac3806a 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -1,5 +1,71 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`builds expected bundles, saves bundle counts to metadata: OptimizerConfig 1`] = ` +OptimizerConfig { + "bundles": Array [ + Bundle { + "cache": BundleCache { + "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public/.kbn-optimizer-cache, + "state": undefined, + }, + "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, + "id": "bar", + "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public, + "publicDirNames": Array [ + "public", + ], + "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, + "type": "plugin", + }, + Bundle { + "cache": BundleCache { + "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public/.kbn-optimizer-cache, + "state": undefined, + }, + "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, + "id": "foo", + "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public, + "publicDirNames": Array [ + "public", + ], + "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, + "type": "plugin", + }, + ], + "cache": true, + "dist": false, + "inspectWorkers": false, + "maxWorkerCount": 1, + "plugins": Array [ + Object { + "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, + "extraPublicDirs": Array [], + "id": "bar", + "isUiPlugin": true, + }, + Object { + "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, + "extraPublicDirs": Array [], + "id": "foo", + "isUiPlugin": true, + }, + Object { + "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/nested/baz, + "extraPublicDirs": Array [], + "id": "baz", + "isUiPlugin": false, + }, + ], + "profileWebpack": false, + "repoRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, + "themeTags": Array [ + "v7dark", + "v7light", + ], + "watch": false, +} +`; + exports[`prepares assets for distribution: bar bundle 1`] = `"(function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!==\\"undefined\\"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:\\"Module\\"})}Object.defineProperty(exports,\\"__esModule\\",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value===\\"object\\"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,\\"default\\",{enumerable:true,value:value});if(mode&2&&typeof value!=\\"string\\")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module[\\"default\\"]}:function getModuleExports(){return module};__webpack_require__.d(getter,\\"a\\",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p=\\"\\";return __webpack_require__(__webpack_require__.s=5)})([function(module,exports,__webpack_require__){\\"use strict\\";var isOldIE=function isOldIE(){var memo;return function memorize(){if(typeof memo===\\"undefined\\"){memo=Boolean(window&&document&&document.all&&!window.atob)}return memo}}();var getTarget=function getTarget(){var memo={};return function memorize(target){if(typeof memo[target]===\\"undefined\\"){var styleTarget=document.querySelector(target);if(window.HTMLIFrameElement&&styleTarget instanceof window.HTMLIFrameElement){try{styleTarget=styleTarget.contentDocument.head}catch(e){styleTarget=null}}memo[target]=styleTarget}return memo[target]}}();var stylesInDom=[];function getIndexByIdentifier(identifier){var result=-1;for(var i=0;i { await del(TMP_DIR); }); -// FLAKY: https://github.com/elastic/kibana/issues/70762 -it.skip('builds expected bundles, saves bundle counts to metadata', async () => { +it('builds expected bundles, saves bundle counts to metadata', async () => { const config = OptimizerConfig.create({ repoRoot: MOCK_REPO_DIR, pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')], @@ -75,7 +74,11 @@ it.skip('builds expected bundles, saves bundle counts to metadata', async () => expect(config).toMatchSnapshot('OptimizerConfig'); const msgs = await runOptimizer(config) - .pipe(logOptimizerState(log, config), toArray()) + .pipe( + logOptimizerState(log, config), + filter((x) => x.event?.type !== 'worker stdio'), + toArray() + ) .toPromise(); const assert = (statement: string, truth: boolean, altStates?: OptimizerUpdate[]) => { @@ -168,8 +171,7 @@ it.skip('builds expected bundles, saves bundle counts to metadata', async () => `); }); -// FLAKY: https://github.com/elastic/kibana/issues/70764 -it.skip('uses cache on second run and exist cleanly', async () => { +it('uses cache on second run and exist cleanly', async () => { const config = OptimizerConfig.create({ repoRoot: MOCK_REPO_DIR, pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')], From da602fc783b5a8d444eb5651314802862502662c Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 6 Jul 2020 14:25:56 -0400 Subject: [PATCH 12/21] fix nav link to be hidden and update access tag (#70607) --- .../security_solution/common/constants.ts | 10 +++++++++ .../security_solution/public/app/types.ts | 10 +-------- .../timeline/routes/create_timelines_route.ts | 2 +- .../timeline/routes/update_timelines_route.ts | 2 +- .../security_solution/server/plugin.ts | 21 ++++++++++++++----- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index f547bc8185d0..d32d9f01d61a 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -35,6 +35,16 @@ export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges'; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51'; export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*'; +export enum SecurityPageName { + alerts = 'alerts', + overview = 'overview', + hosts = 'hosts', + network = 'network', + timelines = 'timelines', + case = 'case', + management = 'management', +} + export const APP_OVERVIEW_PATH = `${APP_PATH}/overview`; export const APP_ALERTS_PATH = `${APP_PATH}/alerts`; export const APP_HOSTS_PATH = `${APP_PATH}/hosts`; diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 4bd888e87bbd..4590f05e1263 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -18,16 +18,8 @@ import { State, SubPluginsInitReducer } from '../common/store'; import { Immutable } from '../../common/endpoint/types'; import { AppAction } from '../common/store/actions'; import { TimelineState } from '../timelines/store/timeline/types'; +export { SecurityPageName } from '../../common/constants'; -export enum SecurityPageName { - alerts = 'alerts', - overview = 'overview', - hosts = 'hosts', - network = 'network', - timelines = 'timelines', - case = 'case', - management = 'management', -} export interface SecuritySubPluginStore { initialState: Record; reducer: Record>; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts index 60ddaea367ae..5bc4bec45dfb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts @@ -33,7 +33,7 @@ export const createTimelinesRoute = ( body: buildRouteValidation(createTimelineSchema), }, options: { - tags: ['access:siem'], + tags: ['access:securitySolution'], }, }, async (context, request, response) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts index f59df151b695..a622ee9b1570 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts @@ -31,7 +31,7 @@ export const updateTimelinesRoute = ( body: buildRouteValidation(updateTimelineSchema), }, options: { - tags: ['access:siem'], + tags: ['access:securitySolution'], }, }, // eslint-disable-next-line complexity diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a97f1eee5634..356b6fca7e8c 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -39,7 +39,7 @@ import { initSavedObjects, savedObjectTypes } from './saved_objects'; import { AppClientFactory } from './client'; import { createConfig$, ConfigType } from './config'; import { initUiSettings } from './ui_settings'; -import { APP_ID, APP_ICON, SERVER_APP_ID } from '../common/constants'; +import { APP_ID, APP_ICON, SERVER_APP_ID, SecurityPageName } from '../common/constants'; import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerResolverRoutes } from './endpoint/routes/resolver'; import { registerPolicyRoutes } from './endpoint/routes/policy'; @@ -70,6 +70,17 @@ export interface PluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PluginStart {} +const securitySubPlugins = [ + APP_ID, + `${APP_ID}:${SecurityPageName.overview}`, + `${APP_ID}:${SecurityPageName.alerts}`, + `${APP_ID}:${SecurityPageName.hosts}`, + `${APP_ID}:${SecurityPageName.network}`, + `${APP_ID}:${SecurityPageName.timelines}`, + `${APP_ID}:${SecurityPageName.case}`, + `${APP_ID}:${SecurityPageName.management}`, +]; + export class Plugin implements IPlugin { private readonly logger: Logger; private readonly config$: Observable; @@ -144,12 +155,12 @@ export class Plugin implements IPlugin Date: Mon, 6 Jul 2020 15:09:35 -0400 Subject: [PATCH 13/21] [EPM][Security Solution] Implementing dataset component templates (#70517) * Implementing dataset component templates * Fixing test * Temporary fix to include timestamp with any component template created * Update package registry docker image for CI. * Adapt to new registry filesystem layout. * Adjust tests to changed registry behavior. * Adding a test for mappings and settings overrides * Wrap all the tests in the docker check Co-authored-by: Elastic Machine Co-authored-by: Sonja Krause-Harder --- .../ingest_manager/common/types/models/epm.ts | 7 ++ .../__snapshots__/template.test.ts.snap | 3 + .../epm/elasticsearch/template/install.ts | 109 +++++++++++++++++- .../elasticsearch/template/template.test.ts | 30 +++++ .../epm/elasticsearch/template/template.ts | 8 +- .../ingest_manager/server/types/index.tsx | 1 + .../0.1.0/dataset/test/fields/fields.yml | 16 +++ .../overrides/0.1.0/dataset/test/manifest.yml | 9 ++ .../overrides/0.1.0/docs/README.md | 3 + .../0.1.0/img/logo_overrides_64_color.svg | 7 ++ .../overrides/0.1.0/manifest.yml | 20 ++++ .../apis/index.js | 1 + .../apis/install.ts | 85 ++++++++++++++ .../apis/list.ts | 2 +- .../apis/template.ts | 1 + 15 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/manifest.yml create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/docs/README.md create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/img/logo_overrides_64_color.svg create mode 100644 x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml create mode 100644 x-pack/test/ingest_manager_api_integration/apis/install.ts 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 3ee3039e9e1c..0d2825f0aa80 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -175,6 +175,12 @@ export interface Dataset { package: string; path: string; ingest_pipeline: string; + elasticsearch?: RegistryElasticsearch; +} + +export interface RegistryElasticsearch { + 'index_template.settings'?: object; + 'index_template.mappings'?: object; } // EPR types this as `[]map[string]interface{}` @@ -272,6 +278,7 @@ export interface IndexTemplate { data_stream: { timestamp_field: string; }; + composed_of: string[]; _meta: object; } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap index f5fec020bf5b..848e65b7931e 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap @@ -94,6 +94,7 @@ exports[`tests loading base.yml: base.yml 1`] = ` "data_stream": { "timestamp_field": "@timestamp" }, + "composed_of": [], "_meta": { "package": { "name": "nginx" @@ -197,6 +198,7 @@ exports[`tests loading coredns.logs.yml: coredns.logs.yml 1`] = ` "data_stream": { "timestamp_field": "@timestamp" }, + "composed_of": [], "_meta": { "package": { "name": "coredns" @@ -1684,6 +1686,7 @@ exports[`tests loading system.yml: system.yml 1`] = ` "data_stream": { "timestamp_field": "@timestamp" }, + "composed_of": [], "_meta": { "package": { "name": "system" diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts index a318aecf347d..e14645bbbf5f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts @@ -5,7 +5,13 @@ */ import Boom from 'boom'; -import { Dataset, RegistryPackage, ElasticsearchAssetType, TemplateRef } from '../../../../types'; +import { + Dataset, + RegistryPackage, + ElasticsearchAssetType, + TemplateRef, + RegistryElasticsearch, +} from '../../../../types'; import { CallESAsCurrentUser } from '../../../../types'; import { Field, loadFieldsFromYaml, processFields } from '../../fields/field'; import { getPipelineNameForInstallation } from '../ingest_pipeline/install'; @@ -157,6 +163,98 @@ export async function installTemplateForDataset({ }); } +function putComponentTemplate( + body: object | undefined, + name: string, + callCluster: CallESAsCurrentUser +): { clusterPromise: Promise; name: string } | undefined { + if (body) { + const callClusterParams: { + method: string; + path: string; + ignore: number[]; + body: any; + } = { + method: 'PUT', + path: `/_component_template/${name}`, + ignore: [404], + body, + }; + + return { clusterPromise: callCluster('transport.request', callClusterParams), name }; + } +} + +function buildComponentTemplates(registryElasticsearch: RegistryElasticsearch | undefined) { + let mappingsTemplate; + let settingsTemplate; + + if (registryElasticsearch && registryElasticsearch['index_template.mappings']) { + mappingsTemplate = { + template: { + mappings: { + ...registryElasticsearch['index_template.mappings'], + // 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 + // we'll need to update this as well + '@timestamp': { + type: 'date', + }, + }, + }, + }, + }; + } + + if (registryElasticsearch && registryElasticsearch['index_template.settings']) { + settingsTemplate = { + template: { + settings: registryElasticsearch['index_template.settings'], + }, + }; + } + return { settingsTemplate, mappingsTemplate }; +} + +async function installDatasetComponentTemplates( + templateName: string, + registryElasticsearch: RegistryElasticsearch | undefined, + callCluster: CallESAsCurrentUser +) { + const templates: string[] = []; + const componentPromises: Array> = []; + + const compTemplates = buildComponentTemplates(registryElasticsearch); + + const mappings = putComponentTemplate( + compTemplates.mappingsTemplate, + `${templateName}-mappings`, + callCluster + ); + + const settings = putComponentTemplate( + compTemplates.settingsTemplate, + `${templateName}-settings`, + callCluster + ); + + if (mappings) { + templates.push(mappings.name); + componentPromises.push(mappings.clusterPromise); + } + + if (settings) { + templates.push(settings.name); + componentPromises.push(settings.clusterPromise); + } + + // TODO: Check return values for errors + await Promise.all(componentPromises); + return templates; +} + export async function installTemplate({ callCluster, fields, @@ -180,13 +278,22 @@ export async function installTemplate({ packageVersion, }); } + + const composedOfTemplates = await installDatasetComponentTemplates( + templateName, + dataset.elasticsearch, + callCluster + ); + const template = getTemplate({ type: dataset.type, templateName, mappings, pipelineName, packageName, + composedOfTemplates, }); + // TODO: Check return values for errors const callClusterParams: { method: string; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts index 73a6767f6b94..99e568bf771f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts @@ -29,10 +29,37 @@ test('get template', () => { templateName, packageName: 'nginx', mappings: { properties: {} }, + composedOfTemplates: [], }); expect(template.index_patterns).toStrictEqual([`${templateName}-*`]); }); +test('adds composed_of correctly', () => { + const composedOfTemplates = ['component1', 'component2']; + + const template = getTemplate({ + type: 'logs', + templateName: 'name', + packageName: 'nginx', + mappings: { properties: {} }, + composedOfTemplates, + }); + expect(template.composed_of).toStrictEqual(composedOfTemplates); +}); + +test('adds empty composed_of correctly', () => { + const composedOfTemplates: string[] = []; + + const template = getTemplate({ + type: 'logs', + templateName: 'name', + packageName: 'nginx', + mappings: { properties: {} }, + composedOfTemplates, + }); + expect(template.composed_of).toStrictEqual(composedOfTemplates); +}); + test('tests loading base.yml', () => { const ymlPath = path.join(__dirname, '../../fields/tests/base.yml'); const fieldsYML = readFileSync(ymlPath, 'utf-8'); @@ -45,6 +72,7 @@ test('tests loading base.yml', () => { templateName: 'foo', packageName: 'nginx', mappings, + composedOfTemplates: [], }); expect(template).toMatchSnapshot(path.basename(ymlPath)); @@ -62,6 +90,7 @@ test('tests loading coredns.logs.yml', () => { templateName: 'foo', packageName: 'coredns', mappings, + composedOfTemplates: [], }); expect(template).toMatchSnapshot(path.basename(ymlPath)); @@ -79,6 +108,7 @@ test('tests loading system.yml', () => { templateName: 'whatsthis', packageName: 'system', mappings, + composedOfTemplates: [], }); expect(template).toMatchSnapshot(path.basename(ymlPath)); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts index 2de378f71753..e7867532ed17 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts @@ -43,14 +43,16 @@ export function getTemplate({ mappings, pipelineName, packageName, + composedOfTemplates, }: { type: string; templateName: string; mappings: IndexTemplateMappings; pipelineName?: string | undefined; packageName: string; + composedOfTemplates: string[]; }): IndexTemplate { - const template = getBaseTemplate(type, templateName, mappings, packageName); + const template = getBaseTemplate(type, templateName, mappings, packageName, composedOfTemplates); if (pipelineName) { template.template.settings.index.default_pipeline = pipelineName; } @@ -244,7 +246,8 @@ function getBaseTemplate( type: string, templateName: string, mappings: IndexTemplateMappings, - packageName: string + packageName: string, + composedOfTemplates: string[] ): IndexTemplate { return { // This takes precedence over all index templates installed by ES by default (logs-*-* and metrics-*-*) @@ -308,6 +311,7 @@ function getBaseTemplate( data_stream: { timestamp_field: '@timestamp', }, + composed_of: composedOfTemplates, _meta: { package: { name: packageName, diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 8239302a9783..a559ca18cfed 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -41,6 +41,7 @@ export { PackageInfo, RegistryVarsEntry, Dataset, + RegistryElasticsearch, AssetReference, ElasticsearchAssetType, IngestAssetType, diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml new file mode 100644 index 000000000000..12a9a03c1337 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml @@ -0,0 +1,16 @@ +- name: dataset.type + type: constant_keyword + description: > + Dataset type. +- name: dataset.name + type: constant_keyword + description: > + Dataset name. +- name: dataset.namespace + type: constant_keyword + description: > + Dataset namespace. +- name: '@timestamp' + type: date + description: > + Event timestamp. diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/manifest.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/manifest.yml new file mode 100644 index 000000000000..9ac3c68a0be9 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/manifest.yml @@ -0,0 +1,9 @@ +title: Test Dataset + +type: logs + +elasticsearch: + index_template.mappings: + dynamic: false + index_template.settings: + index.lifecycle.name: reference diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/docs/README.md b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/docs/README.md new file mode 100644 index 000000000000..17fb41ceae24 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/docs/README.md @@ -0,0 +1,3 @@ +# Test package + +For testing the that the settings and mappings section get used diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/img/logo_overrides_64_color.svg b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/img/logo_overrides_64_color.svg new file mode 100644 index 000000000000..b03007a76ffc --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/img/logo_overrides_64_color.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml new file mode 100644 index 000000000000..ba9fd0fada00 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml @@ -0,0 +1,20 @@ +format_version: 1.0.0 +name: overrides +title: Mappings Settings Test +description: This is a test package for testing that the mappings and settings sections in the dataset manifest are applied. +version: 0.1.0 +categories: ['security'] +release: beta +type: integration +license: basic + +requirement: + elasticsearch: + versions: '>7.7.0' + kibana: + versions: '>7.7.0' + +icons: + - src: '/img/logo_overrides_64_color.svg' + size: '16x16' + type: 'image/svg+xml' diff --git a/x-pack/test/ingest_manager_api_integration/apis/index.js b/x-pack/test/ingest_manager_api_integration/apis/index.js index ef8880f86078..3f8df8379e74 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/index.js +++ b/x-pack/test/ingest_manager_api_integration/apis/index.js @@ -11,5 +11,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./file')); //loadTestFile(require.resolve('./template')); loadTestFile(require.resolve('./ilm')); + loadTestFile(require.resolve('./install')); }); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/install.ts b/x-pack/test/ingest_manager_api_integration/apis/install.ts new file mode 100644 index 000000000000..92078c25419d --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/install.ts @@ -0,0 +1,85 @@ +/* + * 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 '../../api_integration/ftr_provider_context'; +import { warnAndSkipTest } from '../helpers'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const dockerServers = getService('dockerServers'); + const log = getService('log'); + + const deletePackage = async (pkgkey: string) => { + await supertest.delete(`/api/ingest_manager/epm/packages/${pkgkey}`).set('kbn-xsrf', 'xxxx'); + }; + + const mappingsPackage = 'overrides-0.1.0'; + const server = dockerServers.get('registry'); + + describe('installs packages that include settings and mappings overrides', async () => { + after(async () => { + if (server.enabled) { + // remove the package just in case it being installed will affect other tests + await deletePackage(mappingsPackage); + } + }); + + it('should install the overrides package correctly', async function () { + if (server.enabled) { + let { body } = await supertest + .post(`/api/ingest_manager/epm/packages/${mappingsPackage}`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + const templateName = body.response[0].id; + + ({ body } = await es.transport.request({ + method: 'GET', + path: `/_index_template/${templateName}`, + })); + + // make sure it has the right composed_of array, the contents should be the component templates + // that were installed + expect(body.index_templates[0].index_template.composed_of).to.contain( + `${templateName}-mappings` + ); + expect(body.index_templates[0].index_template.composed_of).to.contain( + `${templateName}-settings` + ); + + ({ body } = await es.transport.request({ + method: 'GET', + path: `/_component_template/${templateName}-mappings`, + })); + + // Make sure that the `dynamic` field exists and is set to false (as it is in the package) + expect(body.component_templates[0].component_template.template.mappings.dynamic).to.be( + false + ); + // Make sure that the `@timestamp` field exists and is set to date + // this can be removed once https://github.com/elastic/elasticsearch/issues/58956 is resolved + expect( + body.component_templates[0].component_template.template.mappings.properties['@timestamp'] + .type + ).to.be('date'); + + ({ body } = await es.transport.request({ + method: 'GET', + path: `/_component_template/${templateName}-settings`, + })); + + // Make sure that the lifecycle name gets set correct in the settings + expect( + body.component_templates[0].component_template.template.settings.index.lifecycle.name + ).to.be('reference'); + } else { + warnAndSkipTest(this, log); + } + }); + }); +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/list.ts b/x-pack/test/ingest_manager_api_integration/apis/list.ts index 200358cb6f8f..abed9a7b8595 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/list.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/list.ts @@ -29,7 +29,7 @@ export default function ({ getService }: FtrProviderContext) { return response.body; }; const listResponse = await fetchPackageList(); - expect(listResponse.response.length).to.be(11); + expect(listResponse.response.length).to.be(12); } else { warnAndSkipTest(this, log); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/template.ts b/x-pack/test/ingest_manager_api_integration/apis/template.ts index 8911dd28dc24..f7e5a894b83f 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/template.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/template.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { templateName, mappings, packageName: 'system', + composedOfTemplates: [], }); // This test is not an API integration test with Kibana From a4340f0ecebbc46d77045a83fcc9d1d5cf8fef8b Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 6 Jul 2020 15:10:01 -0400 Subject: [PATCH 14/21] [ML] DF Analytics: add ability to edit job for fields supported by API (#70489) * wip: add edit action to dfanalytics table * add update endpoint and edit flyout * show success and error toasts. close flyout and refresh on success * show permission message in edit action * update types * disable update button if mml not valid * show error in toast, init values are config values * fix undefined check for allow lazy start * prevent update if mml is empty --- x-pack/plugins/ml/common/util/validators.ts | 2 + .../data_frame_analytics/common/analytics.ts | 7 +- .../data_frame_analytics/common/index.ts | 1 + .../components/analytics_list/action_edit.tsx | 66 +++++ .../components/analytics_list/actions.tsx | 6 + .../analytics_list/edit_analytics_flyout.tsx | 270 ++++++++++++++++++ .../ml_api_service/data_frame_analytics.ts | 13 +- .../ml/server/client/elasticsearch_ml.ts | 15 + .../ml/server/routes/data_frame_analytics.ts | 40 +++ .../routes/schemas/data_analytics_schema.ts | 6 + 10 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx diff --git a/x-pack/plugins/ml/common/util/validators.ts b/x-pack/plugins/ml/common/util/validators.ts index 5dcdec055310..c14c20917a13 100644 --- a/x-pack/plugins/ml/common/util/validators.ts +++ b/x-pack/plugins/ml/common/util/validators.ts @@ -67,6 +67,8 @@ export function requiredValidator() { export type ValidationResult = object | null; +export type MemoryInputValidatorResult = { invalidUnits: { allowedUnits: string } } | null; + export function memoryInputValidator(allowedUnits = ALLOWED_DATA_UNITS) { return (value: any) => { if (typeof value !== 'string' || value === '') { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index 5715687402bc..aa637f71db1c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -327,9 +327,14 @@ export const isClassificationEvaluateResponse = ( ); }; +export interface UpdateDataFrameAnalyticsConfig { + allow_lazy_start?: string; + description?: string; + model_memory_limit?: string; +} + export interface DataFrameAnalyticsConfig { id: DataFrameAnalyticsId; - // Description attribute is not supported yet description?: string; dest: { index: IndexName; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts index 58343e26153c..65531009e443 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts @@ -13,6 +13,7 @@ export { useRefreshAnalyticsList, DataFrameAnalyticsId, DataFrameAnalyticsConfig, + UpdateDataFrameAnalyticsConfig, IndexName, IndexPattern, REFRESH_ANALYTICS_LIST_STATE, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx new file mode 100644 index 000000000000..041b52d0322c --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, FC } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; + +import { checkPermission } from '../../../../../capabilities/check_capabilities'; +import { DataFrameAnalyticsListRow } from './common'; + +import { EditAnalyticsFlyout } from './edit_analytics_flyout'; + +interface EditActionProps { + item: DataFrameAnalyticsListRow; +} + +export const EditAction: FC = ({ item }) => { + const canCreateDataFrameAnalytics: boolean = checkPermission('canCreateDataFrameAnalytics'); + + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const closeFlyout = () => setIsFlyoutVisible(false); + const showFlyout = () => setIsFlyoutVisible(true); + + const buttonEditText = i18n.translate('xpack.ml.dataframe.analyticsList.editActionName', { + defaultMessage: 'Edit', + }); + + const editButton = ( + + {buttonEditText} + + ); + + if (!canCreateDataFrameAnalytics) { + return ( + + {editButton} + + ); + } + + return ( + <> + {editButton} + {isFlyoutVisible && } + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index b47b23f66853..b03a3a4c4edb 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -27,6 +27,7 @@ import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } import { stopAnalytics } from '../../services/analytics_service'; import { StartAction } from './action_start'; +import { EditAction } from './action_edit'; import { DeleteAction } from './action_delete'; interface Props { @@ -133,6 +134,11 @@ export const getActions = ( return stopButton; }, }, + { + render: (item: DataFrameAnalyticsListRow) => { + return ; + }, + }, { render: (item: DataFrameAnalyticsListRow) => { return ; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx new file mode 100644 index 000000000000..b6aed9321e4e --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx @@ -0,0 +1,270 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useEffect, useState } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiForm, + EuiFormRow, + EuiOverlayMask, + EuiSelect, + EuiTitle, +} from '@elastic/eui'; + +import { useMlKibana } from '../../../../../contexts/kibana'; +import { ml } from '../../../../../services/ml_api_service'; +import { + memoryInputValidator, + MemoryInputValidatorResult, +} from '../../../../../../../common/util/validators'; +import { extractErrorMessage } from '../../../../../../../common/util/errors'; +import { DataFrameAnalyticsListRow, DATA_FRAME_TASK_STATE } from './common'; +import { + useRefreshAnalyticsList, + UpdateDataFrameAnalyticsConfig, +} from '../../../../common/analytics'; + +interface EditAnalyticsJobFlyoutProps { + closeFlyout: () => void; + item: DataFrameAnalyticsListRow; +} + +let mmLValidator: (value: any) => MemoryInputValidatorResult; + +export const EditAnalyticsFlyout: FC = ({ closeFlyout, item }) => { + const { id: jobId, config } = item; + const { state } = item.stats; + const initialAllowLazyStart = + config.allow_lazy_start !== undefined ? String(config.allow_lazy_start) : ''; + + const [allowLazyStart, setAllowLazyStart] = useState(initialAllowLazyStart); + const [description, setDescription] = useState(config.description || ''); + const [modelMemoryLimit, setModelMemoryLimit] = useState(config.model_memory_limit); + const [mmlValidationError, setMmlValidationError] = useState(); + + const { + services: { notifications }, + } = useMlKibana(); + const { refresh } = useRefreshAnalyticsList(); + + // Disable if mml is not valid + const updateButtonDisabled = mmlValidationError !== undefined; + + useEffect(() => { + if (mmLValidator === undefined) { + mmLValidator = memoryInputValidator(); + } + // validate mml and create validation message + if (modelMemoryLimit !== '') { + const validationResult = mmLValidator(modelMemoryLimit); + if (validationResult !== null && validationResult.invalidUnits) { + setMmlValidationError( + i18n.translate('xpack.ml.dataframe.analytics.create.modelMemoryUnitsInvalidError', { + defaultMessage: 'Model memory limit data unit unrecognized. It must be {str}', + values: { str: validationResult.invalidUnits.allowedUnits }, + }) + ); + } else { + setMmlValidationError(undefined); + } + } else { + setMmlValidationError( + i18n.translate('xpack.ml.dataframe.analytics.create.modelMemoryEmptyError', { + defaultMessage: 'Model memory limit must not be empty', + }) + ); + } + }, [modelMemoryLimit]); + + const onSubmit = async () => { + const updateConfig: UpdateDataFrameAnalyticsConfig = Object.assign( + { + allow_lazy_start: allowLazyStart, + description, + }, + modelMemoryLimit && { model_memory_limit: modelMemoryLimit } + ); + + try { + await ml.dataFrameAnalytics.updateDataFrameAnalytics(jobId, updateConfig); + notifications.toasts.addSuccess( + i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutSuccessMessage', { + defaultMessage: 'Analytics job {jobId} has been updated.', + values: { jobId }, + }) + ); + refresh(); + closeFlyout(); + } catch (e) { + // eslint-disable-next-line + console.error(e); + + notifications.toasts.addDanger({ + title: i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutErrorMessage', { + defaultMessage: 'Could not save changes to analytics job {jobId}', + values: { + jobId, + }, + }), + text: extractErrorMessage(e), + }); + } + }; + + return ( + + + + +

+ {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutTitle', { + defaultMessage: 'Edit {jobId}', + values: { + jobId, + }, + })} +

+
+
+ + + + ) => + setAllowLazyStart(e.target.value) + } + /> + + + setDescription(e.target.value)} + aria-label={i18n.translate( + 'xpack.ml.dataframe.analyticsList.editFlyout.descriptionAriaLabel', + { + defaultMessage: 'Update the job description.', + } + )} + /> + + + setModelMemoryLimit(e.target.value)} + aria-label={i18n.translate( + 'xpack.ml.dataframe.analyticsList.editFlyout.modelMemoryLimitAriaLabel', + { + defaultMessage: 'Update the model memory limit.', + } + )} + /> + + + + + + + + {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutCancelButtonText', { + defaultMessage: 'Cancel', + })} + + + + + {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutUpdateButtonText', { + defaultMessage: 'Update', + })} + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts index 7cdd5478e398..7de39d91047e 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts @@ -8,7 +8,10 @@ import { http } from '../http_service'; import { basePath } from './index'; import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common'; +import { + DataFrameAnalyticsConfig, + UpdateDataFrameAnalyticsConfig, +} from '../../data_frame_analytics/common'; import { DeepPartial } from '../../../../common/types/common'; import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../../../common/types/data_frame_analytics'; @@ -72,6 +75,14 @@ export const dataFrameAnalytics = { body, }); }, + updateDataFrameAnalytics(analyticsId: string, updateConfig: UpdateDataFrameAnalyticsConfig) { + const body = JSON.stringify(updateConfig); + return http({ + path: `${basePath()}/data_frame/analytics/${analyticsId}/_update`, + method: 'POST', + body, + }); + }, evaluateDataFrameAnalytics(evaluateConfig: any) { const body = JSON.stringify(evaluateConfig); return http({ diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts index 07159534e1e2..24c80c450f61 100644 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts +++ b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts @@ -223,6 +223,21 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) method: 'POST', }); + ml.updateDataFrameAnalytics = ca({ + urls: [ + { + fmt: '/_ml/data_frame/analytics/<%=analyticsId%>/_update', + req: { + analyticsId: { + type: 'string', + }, + }, + }, + ], + needBody: true, + method: 'POST', + }); + ml.deleteJob = ca({ urls: [ { diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index e2601c7ad6a2..24be23332e4c 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -10,6 +10,7 @@ import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/a import { RouteInitialization } from '../types'; import { dataAnalyticsJobConfigSchema, + dataAnalyticsJobUpdateSchema, dataAnalyticsEvaluateSchema, dataAnalyticsExplainSchema, analyticsIdSchema, @@ -483,6 +484,45 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {post} /api/ml/data_frame/analytics/:analyticsId/_update Update specified analytics job + * @apiName UpdateDataFrameAnalyticsJob + * @apiDescription Updates a data frame analytics job. + * + * @apiSchema (params) analyticsIdSchema + */ + router.post( + { + path: '/api/ml/data_frame/analytics/{analyticsId}/_update', + validate: { + params: analyticsIdSchema, + body: dataAnalyticsJobUpdateSchema, + }, + options: { + tags: ['access:ml:canCreateDataFrameAnalytics'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const { analyticsId } = request.params; + const results = await context.ml!.mlClient.callAsCurrentUser( + 'ml.updateDataFrameAnalytics', + { + body: request.body, + analyticsId, + } + ); + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + /** * @apiGroup DataFrameAnalytics * diff --git a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts index e6b4e4ccf858..5469c2fefdf3 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts @@ -69,6 +69,12 @@ export const deleteDataFrameAnalyticsJobSchema = schema.object({ deleteDestIndexPattern: schema.maybe(schema.boolean()), }); +export const dataAnalyticsJobUpdateSchema = schema.object({ + description: schema.maybe(schema.string()), + model_memory_limit: schema.maybe(schema.string()), + allow_lazy_start: schema.maybe(schema.boolean()), +}); + export const stopsDataFrameAnalyticsJobQuerySchema = schema.object({ force: schema.maybe(schema.boolean()), }); From 7b0e9dfe9a50b27bc724d9645585aee49fc1a719 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 6 Jul 2020 21:25:52 +0200 Subject: [PATCH 15/21] [SIEM] Unskips and fixes 'Detection rules, custom' test (#70693) * unskips and fixes 'Detection rules, custom' test * deletes comment Co-authored-by: Elastic Machine --- .../alerts_detection_rules_custom.spec.ts | 7 +- .../security_solution/cypress/objects/rule.ts | 6 +- .../custom_rule_with_timeline/data.json.gz | Bin 67934 -> 74563 bytes .../custom_rule_with_timeline/mappings.json | 2349 +++-------------- 4 files changed, 399 insertions(+), 1963 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 2a1a2d2c8e19..51c29c15a809 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { newRule, totalNumberOfPrebuiltRulesInEsArchive } from '../objects/rule'; +import { newRule, totalNumberOfPrebuiltRulesInEsArchiveCustomRule } from '../objects/rule'; import { CUSTOM_RULES_BTN, @@ -64,8 +64,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { ALERTS_URL } from '../urls/navigation'; -// // Skipped as was causing failures on master -describe.skip('Detection rules, custom', () => { +describe('Detection rules, custom', () => { before(() => { esArchiverLoad('custom_rule_with_timeline'); }); @@ -90,7 +89,7 @@ describe.skip('Detection rules, custom', () => { changeToThreeHundredRowsPerPage(); waitForRulesToBeLoaded(); - const expectedNumberOfRules = totalNumberOfPrebuiltRulesInEsArchive + 1; + const expectedNumberOfRules = totalNumberOfPrebuiltRulesInEsArchiveCustomRule + 1; cy.get(RULES_TABLE).then(($table) => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index d750fe212002..c9d3af57e5e5 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -11,6 +11,8 @@ export const totalNumberOfPrebuiltRules = rawRules.length; export const totalNumberOfPrebuiltRulesInEsArchive = 127; +export const totalNumberOfPrebuiltRulesInEsArchiveCustomRule = 145; + interface Mitre { tactic: string; techniques: string[]; @@ -57,7 +59,7 @@ const mitre2: Mitre = { }; export const newRule: CustomRule = { - customQuery: 'host.name: *', + customQuery: 'host.name: * ', name: 'New Rule Test', description: 'The new rule description.', severity: 'High', @@ -67,7 +69,7 @@ export const newRule: CustomRule = { falsePositivesExamples: ['False1', 'False2'], mitre: [mitre1, mitre2], note: '# test markdown', - timelineId: '352c6110-9ffb-11ea-b3d8-857d6042d9bd', + timelineId: '3270f530-bc84-11ea-b73f-89980a6a1ce7', }; export const machineLearningRule: MachineLearningRule = { diff --git a/x-pack/test/security_solution_cypress/es_archives/custom_rule_with_timeline/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/custom_rule_with_timeline/data.json.gz index 3d50451cee39fe8c8f8b49da585473585a812c1d..b3a94c77c11846e2e6d34e2908022b2e943f551b 100644 GIT binary patch literal 74563 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 Z58M!_J$|-*jy<8I@O~(EH^S|5MR~?{>6Tp8 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 diff --git a/x-pack/test/security_solution_cypress/es_archives/custom_rule_with_timeline/mappings.json b/x-pack/test/security_solution_cypress/es_archives/custom_rule_with_timeline/mappings.json index 693878a88f89..01a768351e48 100644 --- a/x-pack/test/security_solution_cypress/es_archives/custom_rule_with_timeline/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/custom_rule_with_timeline/mappings.json @@ -14,36 +14,39 @@ "alert": "7b44fba6773e37c806ce290ea9b7024e", "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", - "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", - "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", "canvas-element": "7390014e1091044523666d97247392fc", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", "cases": "32aa96a6d3855ddda53010ae2048ac22", "cases-comments": "c2061fb929f585df57425102fa928b4b", "cases-configure": "42711cbb311976c0687853f4c1354572", "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "ae24d22d5986d04124cc6568f771066f", + "config": "c63748b75f39d0c54de12d12c1ccbc20", "dashboard": "d00f614b29a80360e1190193fd333bab", - "epm-packages": "92b4b1899b887b090d01c033f3118a85", + "endpoint:exceptions-artifact": "053713a6b91811c7de078ead17384914", + "endpoint:exceptions-manifest": "67c28185da541c1404e7852d30498cd6", + "epm-packages": "04696e7dba1b9597f7d6ed78a4a76658", "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-actions": "00fe5651ed2da16b7f8159bbf0f7d910", "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", - "fleet-agents": "864760267df6c970f629bd4458506c53", - "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "fleet-agents": "578bbfa81650206927683ebde0c85409", + "fleet-enrollment-api-keys": "451e5c329b3ae9722dc7bc8f5921e05d", "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "ingest-agent-configs": "d9a5cbdce8e937f674a7b376c47a34a1", - "ingest-package-configs": "c0fe6347b0eebcbf421841669e3acd31", - "ingest-outputs": "0e57221778a7153c8292edf154099036", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-configs": "f1e09bc73462386a8c07e9d1997d0688", + "ingest-outputs": "87da6a0e27b3a61ad389fb7a7e2da293", + "ingest-package-configs": "48e8bd97e488008e21c0b5a2367b83ad", "ingest_manager_settings": "c5b0749b4ab03c582efd4c14cb8f132c", "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "23d7aa4a720d4938ccde3983f87bd58d", - "maps-telemetry": "bfd39d88aadadb4be597ea984d433dbe", - "metrics-explorer-view": "428e319af3e822c80a84cf87123ca35c", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", "namespace": "2f4316de49999235636386fe51dc06c1", @@ -67,7 +70,7 @@ "upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2", "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", - "url": "b675c3be8d76ecf029294d51dc7ec65d", + "url": "c7f66a0df8b1b52f17c28c4adb111105", "visualization": "52d7a13ad68a150c4525b292d23e12cc" } }, @@ -109,145 +112,6 @@ } } }, - "agent_actions": { - "properties": { - "agent_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "data": { - "type": "flattened" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "agent_configs": { - "properties": { - "datasources": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "text" - }, - "namespace": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "updated_on": { - "type": "keyword" - } - } - }, - "agent_events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "active": { - "type": "boolean" - }, - "config_id": { - "type": "keyword" - }, - "config_newest_revision": { - "type": "integer" - }, - "config_revision": { - "type": "integer" - }, - "current_error_events": { - "type": "text" - }, - "default_api_key": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "text" - }, - "shared_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "text" - }, - "version": { - "type": "keyword" - } - } - }, "alert": { "properties": { "actions": { @@ -1264,29 +1128,12 @@ } }, "application_usage_totals": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - } - } + "dynamic": "false", + "type": "object" }, "application_usage_transactional": { + "dynamic": "false", "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - }, "timestamp": { "type": "date" } @@ -1339,6 +1186,38 @@ } } }, + "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": { @@ -1574,7 +1453,7 @@ } }, "config": { - "dynamic": "true", + "dynamic": "false", "properties": { "buildNum": { "type": "keyword" @@ -1635,132 +1514,39 @@ } } }, - "datasources": { + "endpoint:exceptions-artifact": { "properties": { - "config_id": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "properties": { - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "processors": { - "type": "keyword" - }, - "streams": { - "properties": { - "config": { - "type": "flattened" - }, - "dataset": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "processors": { - "type": "keyword" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "revision": { - "type": "integer" - } - } - }, - "enrollment_api_keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { + "body": { "type": "binary" }, - "api_key_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "created_at": { + "created": { + "index": false, "type": "date" }, - "expire_at": { - "type": "date" - }, - "name": { + "encoding": { + "index": false, "type": "keyword" }, - "type": { + "identifier": { "type": "keyword" }, - "updated_at": { - "type": "date" + "sha256": { + "type": "keyword" + }, + "size": { + "index": false, + "type": "long" } } }, - "epm-package": { + "endpoint:exceptions-manifest": { "properties": { - "installed": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" + "created": { + "index": false, + "type": "date" }, - "internal": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "version": { + "ids": { + "index": false, "type": "keyword" } } @@ -1768,7 +1554,7 @@ "epm-packages": { "properties": { "es_index_patterns": { - "dynamic": "false", + "enabled": false, "type": "object" }, "installed": { @@ -1874,10 +1660,11 @@ "type": "integer" }, "current_error_events": { + "index": false, "type": "text" }, "default_api_key": { - "type": "keyword" + "type": "binary" }, "default_api_key_id": { "type": "keyword" @@ -1894,6 +1681,9 @@ "local_metadata": { "type": "flattened" }, + "packages": { + "type": "keyword" + }, "shared_id": { "type": "keyword" }, @@ -2026,6 +1816,9 @@ } } }, + "inventoryDefaultView": { + "type": "keyword" + }, "logAlias": { "type": "keyword" }, @@ -2061,6 +1854,9 @@ "metricAlias": { "type": "keyword" }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, "name": { "type": "text" } @@ -2068,9 +1864,6 @@ }, "ingest-agent-configs": { "properties": { - "datasources": { - "type": "keyword" - }, "description": { "type": "text" }, @@ -2081,6 +1874,7 @@ "type": "boolean" }, "monitoring_enabled": { + "index": false, "type": "keyword" }, "name": { @@ -2089,6 +1883,9 @@ "namespace": { "type": "keyword" }, + "package_configs": { + "type": "keyword" + }, "revision": { "type": "integer" }, @@ -2103,6 +1900,35 @@ } } }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "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-configs": { "properties": { "config_id": { @@ -2121,6 +1947,7 @@ "type": "boolean" }, "inputs": { + "enabled": false, "properties": { "config": { "type": "flattened" @@ -2128,19 +1955,23 @@ "enabled": { "type": "boolean" }, - "processors": { - "type": "keyword" - }, "streams": { "properties": { - "agent_stream": { + "compiled_stream": { "type": "flattened" }, "config": { "type": "flattened" }, "dataset": { - "type": "keyword" + "properties": { + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } }, "enabled": { "type": "boolean" @@ -2148,9 +1979,6 @@ "id": { "type": "keyword" }, - "processors": { - "type": "keyword" - }, "vars": { "type": "flattened" } @@ -2199,34 +2027,6 @@ } } }, - "ingest-outputs": { - "properties": { - "ca_sha256": { - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, "ingest_manager_settings": { "properties": { "agent_auto_upgrade": { @@ -2387,6 +2187,9 @@ }, "lens": { "properties": { + "description": { + "type": "text" + }, "expression": { "index": false, "type": "keyword" @@ -2420,9 +2223,6 @@ }, "map": { "properties": { - "bounds": { - "type": "geo_shape" - }, "description": { "type": "text" }, @@ -2444,68 +2244,8 @@ } }, "maps-telemetry": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoPointFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoShapeFieldCount": { - "type": "long" - }, - "mapsTotalCount": { - "type": "long" - }, - "settings": { - "properties": { - "showMapVisualizationTypes": { - "type": "boolean" - } - } - }, - "timeCaptured": { - "type": "date" - } - } + "enabled": false, + "type": "object" }, "metrics-explorer-view": { "properties": { @@ -2571,6 +2311,9 @@ } }, "type": "nested" + }, + "source": { + "type": "keyword" } } } @@ -2579,6 +2322,24 @@ "migrationVersion": { "dynamic": "true", "properties": { + "alert": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, "dashboard": { "fields": { "keyword": { @@ -2597,24 +2358,6 @@ }, "type": "text" }, - "ingest-agent-configs": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "ingest-package-configs": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, "map": { "fields": { "keyword": { @@ -2670,37 +2413,6 @@ "namespaces": { "type": "keyword" }, - "outputs": { - "properties": { - "api_key": { - "type": "keyword" - }, - "ca_sha256": { - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, "query": { "properties": { "description": { @@ -2784,6 +2496,7 @@ } }, "server": { + "dynamic": "strict", "properties": { "uuid": { "type": "keyword" @@ -3208,6 +2921,9 @@ } } }, + "spaceId": { + "type": "keyword" + }, "telemetry": { "properties": { "allowChangingOptInStatus": { @@ -3424,6 +3140,7 @@ "url": { "fields": { "keyword": { + "ignore_above": 2048, "type": "keyword" } }, @@ -3489,14 +3206,6 @@ }, "agent": { "properties": { - "build": { - "properties": { - "original": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "ephemeral_id": { "ignore_above": 1024, "type": "keyword" @@ -3519,6 +3228,27 @@ } } }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, "client": { "properties": { "address": { @@ -3684,10 +3414,6 @@ "id": { "ignore_above": 1024, "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" } } }, @@ -3715,18 +3441,6 @@ } } }, - "project": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "provider": { "ignore_above": 1024, "type": "keyword" @@ -3737,6 +3451,27 @@ } } }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, "container": { "properties": { "id": { @@ -3949,9 +3684,6 @@ } } }, - "compile_time": { - "type": "date" - }, "hash": { "properties": { "md5": { @@ -3972,53 +3704,6 @@ } } }, - "malware_classification": { - "properties": { - "features": { - "properties": { - "data": { - "properties": { - "buffer": { - "ignore_above": 1024, - "type": "keyword" - }, - "decompressed_size": { - "type": "integer" - }, - "encoding": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "identifier": { - "ignore_above": 1024, - "type": "keyword" - }, - "score": { - "type": "double" - }, - "threshold": { - "type": "double" - }, - "upx_packed": { - "type": "boolean" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "mapped_address": { - "ignore_above": 1024, - "type": "keyword" - }, - "mapped_size": { - "type": "long" - }, "name": { "ignore_above": 1024, "type": "keyword" @@ -4029,10 +3714,6 @@ }, "pe": { "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, "company": { "ignore_above": 1024, "type": "keyword" @@ -4045,10 +3726,6 @@ "ignore_above": 1024, "type": "keyword" }, - "imphash": { - "ignore_above": 1024, - "type": "keyword" - }, "original_file_name": { "ignore_above": 1024, "type": "keyword" @@ -4147,46 +3824,6 @@ } } }, - "endpoint": { - "properties": { - "artifact": { - "properties": { - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "event": { - "properties": { - "process": { - "properties": { - "ancestry": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "policy": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, "error": { "properties": { "code": { @@ -4360,9 +3997,6 @@ "ignore_above": 1, "type": "keyword" }, - "entry_modified": { - "type": "double" - }, "extension": { "ignore_above": 1024, "type": "keyword" @@ -4399,154 +4033,6 @@ "ignore_above": 1024, "type": "keyword" }, - "macro": { - "properties": { - "code_page": { - "type": "long" - }, - "collection": { - "properties": { - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "errors": { - "properties": { - "count": { - "type": "long" - }, - "error_type": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "type": "nested" - }, - "file_extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "project_file": { - "properties": { - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "stream": { - "properties": { - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "raw_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "raw_code_size": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "type": "nested" - } - } - }, - "malware_classification": { - "properties": { - "features": { - "properties": { - "data": { - "properties": { - "buffer": { - "ignore_above": 1024, - "type": "keyword" - }, - "decompressed_size": { - "type": "integer" - }, - "encoding": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "identifier": { - "ignore_above": 1024, - "type": "keyword" - }, - "score": { - "type": "double" - }, - "threshold": { - "type": "double" - }, - "upx_packed": { - "type": "boolean" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "mime_type": { "ignore_above": 1024, "type": "keyword" @@ -4578,10 +4064,6 @@ }, "pe": { "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, "company": { "ignore_above": 1024, "type": "keyword" @@ -4594,10 +4076,6 @@ "ignore_above": 1024, "type": "keyword" }, - "imphash": { - "ignore_above": 1024, - "type": "keyword" - }, "original_file_name": { "ignore_above": 1024, "type": "keyword" @@ -4608,13 +4086,6 @@ } } }, - "quarantine_path": { - "ignore_above": 1024, - "type": "keyword" - }, - "quarantine_result": { - "type": "boolean" - }, "size": { "type": "long" }, @@ -4628,10 +4099,6 @@ "ignore_above": 1024, "type": "keyword" }, - "temp_file_path": { - "ignore_above": 1024, - "type": "keyword" - }, "type": { "ignore_above": 1024, "type": "keyword" @@ -4639,112 +4106,41 @@ "uid": { "ignore_above": 1024, "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" }, - "x509": { - "properties": { - "alternative_names": { - "ignore_above": 1024, - "type": "keyword" - }, - "issuer": { - "properties": { - "common_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country": { - "ignore_above": 1024, - "type": "keyword" - }, - "distinguished_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "locality": { - "ignore_above": 1024, - "type": "keyword" - }, - "organization": { - "ignore_above": 1024, - "type": "keyword" - }, - "organizational_unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "state_or_province": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "public_key_algorithm": { - "ignore_above": 1024, - "type": "keyword" - }, - "public_key_curve": { - "ignore_above": 1024, - "type": "keyword" - }, - "public_key_exponent": { - "doc_values": false, - "index": false, - "type": "long" - }, - "public_key_size": { - "type": "long" - }, - "serial_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "signature_algorithm": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject": { - "properties": { - "common_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country": { - "ignore_above": 1024, - "type": "keyword" - }, - "distinguished_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "locality": { - "ignore_above": 1024, - "type": "keyword" - }, - "organization": { - "ignore_above": 1024, - "type": "keyword" - }, - "organizational_unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "state_or_province": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version_number": { - "ignore_above": 1024, - "type": "keyword" - } - } + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" } } }, @@ -4764,6 +4160,26 @@ } } }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "host": { "properties": { "architecture": { @@ -4862,10 +4278,6 @@ "ignore_above": 1024, "type": "keyword" }, - "variant": { - "ignore_above": 1024, - "type": "keyword" - }, "version": { "ignore_above": 1024, "type": "keyword" @@ -4995,10 +4407,6 @@ }, "status_code": { "type": "long" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" } } }, @@ -5008,19 +4416,27 @@ } } }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "labels": { "type": "object" }, "log": { "properties": { - "file": { - "properties": { - "path": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "level": { "ignore_above": 1024, "type": "keyword" @@ -5320,10 +4736,6 @@ "ignore_above": 1024, "type": "keyword" }, - "variant": { - "ignore_above": 1024, - "type": "keyword" - }, "version": { "ignore_above": 1024, "type": "keyword" @@ -5370,6 +4782,46 @@ } } }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "package": { "properties": { "architecture": { @@ -5424,6 +4876,30 @@ } } }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "process": { "properties": { "args": { @@ -5501,46 +4977,6 @@ } } }, - "malware_classification": { - "properties": { - "features": { - "properties": { - "data": { - "properties": { - "buffer": { - "ignore_above": 1024, - "type": "keyword" - }, - "decompressed_size": { - "type": "integer" - }, - "encoding": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "identifier": { - "ignore_above": 1024, - "type": "keyword" - }, - "score": { - "type": "double" - }, - "threshold": { - "type": "double" - }, - "upx_packed": { - "type": "boolean" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "name": { "fields": { "text": { @@ -5688,10 +5124,6 @@ }, "pe": { "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, "company": { "ignore_above": 1024, "type": "keyword" @@ -5704,10 +5136,6 @@ "ignore_above": 1024, "type": "keyword" }, - "imphash": { - "ignore_above": 1024, - "type": "keyword" - }, "original_file_name": { "ignore_above": 1024, "type": "keyword" @@ -5727,132 +5155,17 @@ "ppid": { "type": "long" }, - "services": { - "ignore_above": 1024, - "type": "keyword" - }, "start": { "type": "date" }, "thread": { "properties": { - "call_stack": { - "properties": { - "instruction_pointer": { - "ignore_above": 1024, - "type": "keyword" - }, - "memory_section": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "protection": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "module_path": { - "ignore_above": 1024, - "type": "keyword" - }, - "rva": { - "ignore_above": 1024, - "type": "keyword" - }, - "symbol_info": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "id": { "type": "long" }, "name": { "ignore_above": 1024, "type": "keyword" - }, - "service": { - "ignore_above": 1024, - "type": "keyword" - }, - "start": { - "type": "date" - }, - "start_address": { - "ignore_above": 1024, - "type": "keyword" - }, - "start_address_module": { - "ignore_above": 1024, - "type": "keyword" - }, - "token": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "elevation": { - "type": "boolean" - }, - "elevation_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "impersonation_level": { - "ignore_above": 1024, - "type": "keyword" - }, - "integrity_level": { - "type": "long" - }, - "integrity_level_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "is_appcontainer": { - "type": "boolean" - }, - "privileges": { - "properties": { - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "type": "nested" - }, - "sid": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "uptime": { - "type": "long" } } }, @@ -5866,70 +5179,9 @@ "ignore_above": 1024, "type": "keyword" }, - "token": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "elevation": { - "type": "boolean" - }, - "elevation_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "impersonation_level": { - "ignore_above": 1024, - "type": "keyword" - }, - "integrity_level": { - "type": "long" - }, - "integrity_level_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "is_appcontainer": { - "type": "boolean" - }, - "privileges": { - "properties": { - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "type": "nested" - }, - "sid": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, "uptime": { "type": "long" }, - "user": { - "ignore_above": 1024, - "type": "keyword" - }, "working_directory": { "fields": { "text": { @@ -6342,6 +5594,12 @@ }, "rule": { "properties": { + "author": { + "type": "keyword" + }, + "building_block_type": { + "type": "keyword" + }, "created_at": { "type": "date" }, @@ -6378,6 +5636,9 @@ "language": { "type": "keyword" }, + "license": { + "type": "keyword" + }, "max_signals": { "type": "keyword" }, @@ -6399,15 +5660,47 @@ "risk_score": { "type": "keyword" }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, "rule_id": { "type": "keyword" }, + "rule_name_override": { + "type": "keyword" + }, "saved_id": { "type": "keyword" }, "severity": { "type": "keyword" }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, "size": { "type": "keyword" }, @@ -6453,6 +5746,9 @@ "timeline_title": { "type": "keyword" }, + "timestamp_override": { + "type": "keyword" + }, "to": { "type": "keyword" }, @@ -6637,663 +5933,6 @@ "ignore_above": 1024, "type": "keyword" }, - "target": { - "properties": { - "dll": { - "properties": { - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "compile_time": { - "type": "date" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "malware_classification": { - "properties": { - "features": { - "properties": { - "data": { - "properties": { - "buffer": { - "ignore_above": 1024, - "type": "keyword" - }, - "decompressed_size": { - "type": "integer" - }, - "encoding": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "identifier": { - "ignore_above": 1024, - "type": "keyword" - }, - "score": { - "type": "double" - }, - "threshold": { - "type": "double" - }, - "upx_packed": { - "type": "boolean" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "mapped_address": { - "ignore_above": 1024, - "type": "keyword" - }, - "mapped_size": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "pe": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "company": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "file_version": { - "ignore_above": 1024, - "type": "keyword" - }, - "imphash": { - "ignore_above": 1024, - "type": "keyword" - }, - "original_file_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "product": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "process": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "malware_classification": { - "properties": { - "features": { - "properties": { - "data": { - "properties": { - "buffer": { - "ignore_above": 1024, - "type": "keyword" - }, - "decompressed_size": { - "type": "integer" - }, - "encoding": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "identifier": { - "ignore_above": 1024, - "type": "keyword" - }, - "score": { - "type": "double" - }, - "threshold": { - "type": "double" - }, - "upx_packed": { - "type": "boolean" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "parent": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pe": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "company": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "file_version": { - "ignore_above": 1024, - "type": "keyword" - }, - "imphash": { - "ignore_above": 1024, - "type": "keyword" - }, - "original_file_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "product": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "services": { - "ignore_above": 1024, - "type": "keyword" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "call_stack": { - "properties": { - "instruction_pointer": { - "ignore_above": 1024, - "type": "keyword" - }, - "memory_section": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "protection": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "module_path": { - "ignore_above": 1024, - "type": "keyword" - }, - "rva": { - "ignore_above": 1024, - "type": "keyword" - }, - "symbol_info": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "service": { - "ignore_above": 1024, - "type": "keyword" - }, - "start": { - "type": "date" - }, - "start_address": { - "ignore_above": 1024, - "type": "keyword" - }, - "start_address_module": { - "ignore_above": 1024, - "type": "keyword" - }, - "token": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "elevation": { - "type": "boolean" - }, - "elevation_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "impersonation_level": { - "ignore_above": 1024, - "type": "keyword" - }, - "integrity_level": { - "type": "long" - }, - "integrity_level_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "is_appcontainer": { - "type": "boolean" - }, - "privileges": { - "properties": { - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "type": "nested" - }, - "sid": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "uptime": { - "type": "long" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "token": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "elevation": { - "type": "boolean" - }, - "elevation_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "impersonation_level": { - "ignore_above": 1024, - "type": "keyword" - }, - "integrity_level": { - "type": "long" - }, - "integrity_level_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "is_appcontainer": { - "type": "boolean" - }, - "privileges": { - "properties": { - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "type": "nested" - }, - "sid": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "uptime": { - "type": "long" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, "threat": { "properties": { "framework": { @@ -7397,112 +6036,6 @@ "supported_ciphers": { "ignore_above": 1024, "type": "keyword" - }, - "x509": { - "properties": { - "alternative_names": { - "ignore_above": 1024, - "type": "keyword" - }, - "issuer": { - "properties": { - "common_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country": { - "ignore_above": 1024, - "type": "keyword" - }, - "distinguished_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "locality": { - "ignore_above": 1024, - "type": "keyword" - }, - "organization": { - "ignore_above": 1024, - "type": "keyword" - }, - "organizational_unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "state_or_province": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "public_key_algorithm": { - "ignore_above": 1024, - "type": "keyword" - }, - "public_key_curve": { - "ignore_above": 1024, - "type": "keyword" - }, - "public_key_exponent": { - "doc_values": false, - "index": false, - "type": "long" - }, - "public_key_size": { - "type": "long" - }, - "serial_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "signature_algorithm": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject": { - "properties": { - "common_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country": { - "ignore_above": 1024, - "type": "keyword" - }, - "distinguished_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "locality": { - "ignore_above": 1024, - "type": "keyword" - }, - "organization": { - "ignore_above": 1024, - "type": "keyword" - }, - "organizational_unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "state_or_province": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version_number": { - "ignore_above": 1024, - "type": "keyword" - } - } } } }, @@ -7563,112 +6096,6 @@ "subject": { "ignore_above": 1024, "type": "keyword" - }, - "x509": { - "properties": { - "alternative_names": { - "ignore_above": 1024, - "type": "keyword" - }, - "issuer": { - "properties": { - "common_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country": { - "ignore_above": 1024, - "type": "keyword" - }, - "distinguished_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "locality": { - "ignore_above": 1024, - "type": "keyword" - }, - "organization": { - "ignore_above": 1024, - "type": "keyword" - }, - "organizational_unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "state_or_province": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "public_key_algorithm": { - "ignore_above": 1024, - "type": "keyword" - }, - "public_key_curve": { - "ignore_above": 1024, - "type": "keyword" - }, - "public_key_exponent": { - "doc_values": false, - "index": false, - "type": "long" - }, - "public_key_size": { - "type": "long" - }, - "serial_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "signature_algorithm": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject": { - "properties": { - "common_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country": { - "ignore_above": 1024, - "type": "keyword" - }, - "distinguished_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "locality": { - "ignore_above": 1024, - "type": "keyword" - }, - "organization": { - "ignore_above": 1024, - "type": "keyword" - }, - "organizational_unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "state_or_province": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version_number": { - "ignore_above": 1024, - "type": "keyword" - } - } } } }, @@ -7879,10 +6306,6 @@ "ignore_above": 1024, "type": "keyword" }, - "variant": { - "ignore_above": 1024, - "type": "keyword" - }, "version": { "ignore_above": 1024, "type": "keyword" @@ -7895,6 +6318,18 @@ } } }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "vulnerability": { "properties": { "category": { From cd508994931d26b0c7266b404b287df8a6eb6b98 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 6 Jul 2020 21:26:34 +0200 Subject: [PATCH 16/21] fixes and unskips 'export rule' test (#70699) Co-authored-by: Elastic Machine --- .../alerts_detection_rules_export.spec.ts | 7 +- .../test_files/expected_rules_export.ndjson | 2 +- .../es_archives/export_rule/data.json.gz | Bin 0 -> 28233 bytes .../es_archives/export_rule/mappings.json | 6415 +++++++++++++++++ 4 files changed, 6419 insertions(+), 5 deletions(-) create mode 100644 x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz create mode 100644 x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index 06e9228de4f4..fdab3016de8d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -17,10 +17,9 @@ import { ALERTS_URL } from '../urls/navigation'; const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_export.ndjson'; -// Skipped as was causing failures on master -describe.skip('Export rules', () => { +describe('Export rules', () => { before(() => { - esArchiverLoad('custom_rules'); + esArchiverLoad('export_rule'); cy.server(); cy.route( 'POST', @@ -29,7 +28,7 @@ describe.skip('Export rules', () => { }); after(() => { - esArchiverUnload('custom_rules'); + esArchiverUnload('export_rule'); }); it('Exports a custom rule', () => { diff --git a/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson b/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson index dcbfa9d0dd16..7baa59fb3d8c 100644 --- a/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson +++ b/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson @@ -1,2 +1,2 @@ -{"actions":[],"created_at":"2020-03-26T10:09:07.569Z","updated_at":"2020-03-26T10:09:08.021Z","created_by":"elastic","description":"Rule 1","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","id":"49db5bd1-bdd5-4821-be26-bb70a815dedb","immutable":false,"index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"0cea4194-03f2-4072-b281-d31b72221d9d","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":50,"name":"Rule 1","query":"host.name:*","references":[],"meta":{"from":"1m","throttle":"no_actions"},"severity":"low","updated_by":"elastic","tags":["rule1"],"to":"now","type":"query","threat":[],"throttle":"no_actions","version":1,"exceptions_list":[]} +{"author":[],"actions":[],"created_at":"2020-07-03T10:44:10.567Z","updated_at":"2020-07-03T10:44:10.941Z","created_by":"elastic","description":"Export rule","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","id":"ad65b1b6-be18-4e41-9d0a-89d8576053d8","immutable":false,"index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"50a3776b-144d-4cff-9f1f-1173e0d5d4a4","language":"kuery","license":"","output_index":".siem-signals-default","max_signals":100,"risk_score":50,"risk_score_mapping":[],"rule_name_override":"","name":"Export rule","query":"host.name: * ","references":[],"meta":{"from":"1m","kibana_siem_app_url":"http://localhost:5620/app/security"},"severity":"low","severity_mapping":[],"updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[],"throttle":"no_actions","timestamp_override":"","version":1,"exceptions_list":[]} {"exported_count":1,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9501cf5e0586e96a97076fd0d71b5db6485eb6ea GIT binary patch literal 28233 zcmV)TK(W6ciwFP!000026YRZvliNm;FZ#bf1-EB+&)By~;r-}{y}Pz7&G>{R`C6Je zv192l3P7P7+aSOJz?OPE{oQZ900JPMe5skFV?1U9g{u73BQq;2EA#hnCX=h|afz>P zCRbkUUR~1%4=7yGC%=Vhji$os&LQBo=0|!ND zF6VF3c$K*8$|lu{O3&$=bj~W8FHx4^B&xn91yG2Sj6S^z z@n%{4fTJv((UN$lb@ZEZ)quYZ={+aS$4x-&0O|1ibwDhaao z5Ay=`4>KHMim4e~@s@m6eD$=hfK1{vLt%*NbpNeXZ+Qw9%fV6%Q@4F-YD%Uym1T^k zw&Pk;!%;0?^AsD&MuocaLPP1D6QjhtD!qTY?rTzZeHVL(G^tq5$R;0xbcMp;A1Djr zXk@ckS9ICl3h55b>bh!~rcTk??(v$n6-UONOVJwWc+Daesg5VJX2*{00xici*2Fy9 zD(4J2!;58zGQ3MWCB}QS#&V8+fc9Fs^-&wk#mBKaG?zKYz$gUOv7qH*?pW;=^6^*= z7IO6%gSVE&<6A3!f2v4lsuH4*eCwky#idsfTCgf>TB*=N7V!gem?l{N?g1w$<<#U; zX=cr&oQMK0`P#<`j$Edx<~l(^Re`0V(iU%5OR_?-H%A#QqezN0mCUK6evqXbO}$aA znQUu+_>4W3UKr6DCprS`63kM;OSDS07Mvi?f{d&6bFC5&82r@IGGMkoWArAFl|T z3ZmPOS5McipN#)v{3qF6f{`~H@!c38BZ`hF+fOr_`Gh(DeMP5dZ!yBO2oG;9HfAVC zjNzEU8$D`+hHJ<;4H}@~BjXW%6-12p%$&yzr*PtW-#@Rp@8+G`2S1C?lty+o*E>EhX zI}?ty0sCC|5d#jK8wDOzWRMpp-&#jC{&UyU#!kjz$URI)p8T0&Z=MGK zIHAFhRttv?+U}izBRBR~hn~X(SgU#Gfj4#I1fMW#Zh=KlJJP#2^x#2TEvd(-dyh{@ zadqN2#3q$W#w{UErm7eE+IeIH_0AZ28Qh<==0uCqz^em1x&*50}e2z5F z<2dsijxybNu|SbG56LDF+$3E?n`QW@?pTKj+*SifkJz$Dc+N|NPIo051PTo1eEbq* zA}%E;1r6BUmkKOE_h0I`;M{?!BZG4fCPxNPN2bp~vUwg=`h<7(g@B@8#pUX;Ec2Yz^5q<2mxC+z@Dw-@Sq;CY~TYuNr4`?2zWrxUCQVH9>3J! z!8?Vi0fcuLBLKoxzkwgkh%}xMMFLox)DkSRG($-SB1J`eT3v|aCg8!90xa`6U}%sf z+-iARiEqX%Jes+gbi~{X66|I%xcGZ*Fk#MDXklbaz~J6Lg&9t^gbr*-*%C&WVPzri z?cgDT2MOLbPHb>a+YNLLV&l&N;t@ixzMc&JC1cgK@Sag&31_113(C`nxbVf;rM?nUcS$GtBDCvZIK*Px)+ynIF5(oF-Imv9n zJ(vKc2<|}&MPV*bE|)||0P$0*p@Jg%V8-=pKn5dTgy>LZFaTmfmEi!0{6yhEVc8Ha!7vQq5e6Cv&RukGe`?-cIn5`W=ClO@lylBDcrXq-ThReJ@od8g<=C?g zAB?ll0v!=k$s>(XL%rs_ljM*K3ly(0B80!kT^-=Bt2n%!U4=cq%CfS#{M&hw1E`(*jX|DlHmx#xnvF8GKrajPamJ~ z@+!lCDzG;(0uq=Dzwk%-}ApkAn~8#(E!WI9JyDke=L~^*%r+d1<{56rfw{ zeNch;#uqU2`xqxV9dqJmx2Nxx*#IhuS4()X7Lb(f0u`tYQh_S|90AfX3Rn=XaBiYn z0b+uGU!epM9XEoJbgl^((F0du21jAa@M6htb%GkP&xFeO^JyM@3)*M@h{=2dwrNDJ z%gh|7eSw8T$p}nmm0=i8tbo0b;d&lJ_d15`aSYen7{q;Ej^y;C1v4BThLB5_LxGZ& zf}=@-2RsjPmH|=lVEOw&u~5P>N`mqzg5^L2&0h$bdr%NTvEeU>RtCpo9?a3o0*JN@ zX8R21AP9_cngW55Z;rzO6OJ^811KJ84hd2~(i{`Kh@?3(AR$Q^95BwT8A~Aw=Zv(3 z2aq%Zvn2rF6v9kSrxs4CAbLj1ho?*V4AA`HfFMYC4#|rbD2QNSfile?%&xmQ&1SUe zAj9tS4}v!k5RBErpxokk<_6j09L#KEh+^g#8D-F5tysLYAdNv>G~ECOYP7s`hQ@+oW6F-VVscX2BozD!UDM7sU)NGS_D4WJAC@^x8!2)z9!0=!KCxios zD$R3PGp>LHNI_%3PSQ)Gf&3GeJm5SxTA{(>36Q)w>ZssNLRfPTLT2VQ8c4GZ);xow z@+^~>MjkpfRi{Mu3`OK;cKU%2DwtN;9Sv~B-8bf^&k4%lPB$lQI|?X)p*j%4h!52T z5Kx$?4wz7)MRh=g5j3g{@Qji~jD`&^PsHdRfRZX=0B|^gKL>z4xr7k|Fr8%1hyl0& zrHvSX7RZ@@87bM=(`bMxEKDi1Vanor909Q}Z{orsemJo!8v~d~4I79URh#&N#gcHv zIN}%y*wc3?YycT$_%@-}^-c_6zpC8`5S%qYpiJjzxulmA2^&2nU3xILSth|l5aL^$ zo~oi*#{nq@QzuRw(WX7nlX3==ucWY!DmW;Z>Iyx{goFwb9+cdskg&`J4j9TJ?4v)> z$9`vUR0h@YXg6zaX z#&qjcP!gC_@Ic66QU?SejY%C6fJ`PiBuI)ZJTL1dN^o?Nj@F#=Q*{$xQxCxTxD^aE zLqt1JST>0coB+)e?P#G{D%z34GFp^yp4xTe76`NDDGg8e#!5hgbfQp#1nXcy06Pn@ zehwo2e8l;Ai1Kp~;}DM^8lXWmEWkUE$Z>%?m)HOXb3Q^lr4YEF9afZKK{~Uj;R1AY zQ9}mk1VbPLs3Su?&zLQ5!I?|o8Gx4XAf*{fPRRD-p$zvRqL-tQHv>0P0vAB0PBvU2 zNK7Cl6^y9pHBg`9yub^h^vzSuFwc;ya8z@+qOyW-&bcT+m=J>V^ROKvsL+QS zFabwE+{6kq5Mm!rQ0Bo+ut38iZU8;)=!hL~Af7yQ!G#tpu?s6`)&nSV;s!R*N)M#C zgC%gVTqVFcRsr;=0_92t(1!{ZM*!)A%fwt#Gqh;Bm{xPUxhH-H>zl0$VZ0hHi=iulawU|<)YPLmOjS?NXBqLpk^JI$wotLfdB$zdV!f(aC8^^aDur(3ZiFjoFq6rfg1<|003C! z9!|4>Um<{U*S8GGdCYKn>P|FeKrr;MGNNF4I%p`Z?MRl$y~i1->9&lM3=$sFSdeBo z!QLF+O3LD;xHbd?Ta=!fo+C(}#mqY^1u_CBmoKD+5fC(gU(p3a77K)(O$87<)>cNB zt{i}2{;w*OK5X$gm35wFwGWhoZ0$_kh z<5l8fC`Ypb4G3u}u)r|f2{5N^;SwN#h?yJ>fD>2&1>6aRzy-~UPZton1rh{+&AH@p z=8<+zP4EnX#LK_|br(FqnRNH~5eyaxSrZB{tjZHa^XcmPn~C`6iUtUK9pKP=OYmLY zOs@WmK1nmQSXPT=A$uLhx9Rk|EAi$jFLJdAZWHD~^jB{3CuM(Mf6jVfN=BbZZu;uV zoEbA|vzFqDaY_pPCZ2vOUQ;ae>uPny>(!FR+V|!tqlFYnk*1P4mDCTibfc*^DrwY| z|HfN=`iB1d^vySv%A#V@x2(D=*1-jatH9f6_GW=ehq|;guD@A>q~&J!fkQ1Xa{h+P z_)?c_bV#YMd^#Gh<79!dH)u)gBeO4kBudYM`VC_izH+vt3DR!J8)|-|@)na8=p=l) zu9gKispqrPBcHSP;OVB@|aqc9G< z7`kyEvGE)A2_MA4pLjufAH*{jCRJ$Jr7y&j&GMgcs(BxHHe9oI4 z^F6_v#LLG)eL)j@il68K1)v1eW;_wH%wXLha5`H|neI*q24kS?azdI0C z%Wg>c*K_Rqfg6y`!sC`>!?A2x&)w=4Gx{55Y&(keS7YaG?aQqw-I(@62u(1}Bv<36 zLq1Gb$ueA}qrda8k5*we=cyz+`zAl6i&xp8zAVngqweC>)Xk+bYl}cFCvsnzu0r`{ zL6jY*{ce6WSl!A$%~@T4f7iZ9$j>3&-tgHr^xdIkD1j~yX&}B_Fa?BKqsLI&R>uN; z*|DB5SOnQ_b)=v7fvBiuXH2A~T~XcdW0>yuHBR^Y7${oFJadC2^uI+ zW3^l{Xprzg+bq^x9>IWuw)v>IkHUcjPMs~g;=I^?pn)_A`7LCCz{&&Yxw(poeXs4x-Tlmnix@Q0D( zggcHo$3YLbA`k;WkObli!-fcVhKjqVbFfORvq~R<0LufP6_L#5PLQR*z`;3=YmPk! z4Vs4;{{UpgEr;}jkhWw4)q;YVFXQ0!6Vx@tY@|QoEi)c;3?iyI&=C^>?V!ObN8AI6 z4UhuDL$g9U)v83B1;sd80`>w0#{&xk1qX@qhcWVIG9)lMsF4AMhWX>l;1B^EBT$ea zU5l;;)`tWM+{UAlr~uqZ!P!Q&r+okj58iCtNTUG>TsheXI9yPdS}A!7I9N1?gh)sN z4jYTc3=;)Az|Y|5Aj6JL?o5hlE9{z;%i2-xD$fRYdxI>@~T z0yIyu1;}-|A(Jbpp;{+Y0|kq-5RvB%+JJ!thLa>tAWlV)z{r4kNlepb0vs`r$mV%g z%3wMB?s-ES1cN5!gn)x42?c^<9@6{g5WocP;>1QJ&$DO0t+B0fUrJw2_1V%N$sxeG50 zB50^^^l*Y2XfY-bqt2JX@mv9{39#tkoRMx~1D55X3=m*qgw64TijD~ABs;OdTOQLX zI-%trsc_DHYseDv6VfSE13Y-#GM|-qD1kODeQy>oaRd)l7%YN}CG>!bjm$<5nq<38 z;?;6?O6I9yTThN_iLyJG$awN5xY%Hioa?7FUUU$9vsDU{rQ|UGz9QQK@F zpI@E@)ZFU{I9MW3$j?(g4CHu(wGApeEiT1E<)#AQg2hBew-}_oyAA6CeK`}n+z5Fd zSbY?Ps{}sK^z%6_*#hm^ELwv2jFv4ydln1#{+vYZ{kh57`*RYu_h%z*DbhK;)pbq_ zw*>fn7B2&RP79a;K8r=n?Mukbf(JZDQ5>DY2v;!EEIMa{nZ^D*8?<);PAGAKi^4g7 zGd~wD*4zW>=QHboAUa3ZR-isB=C(jI2*i0|W`QYLq_OiD;s1$~`FUYxLCEGobT&}q zRdxngq28is!8)h$^v(t_3mJGWU{?`MfzGIajM)kAgBa|QK1T=lM4#j1-cjHuDTf)v z&_EWE!~jJluVYJAQN(67NOXZ0VxZ8{5aT7>kxMg3Ar|ZC{t+*n0P4*2NQFS5t)lxV z{tRVroS?vi6powWp__zLy8F|QD8vsq1T{pDkvpdr<2Zr@%L~%`{Fo0Chv2v%DZwRJ z=@L$aiV`g9O8`cDDi0TpvX`D@2w{f831b)HViLG0X9qLwXg;LC#gH_l98wq~Zw@J9 zWeyA^rj-MCtmbJzSO$oQY&kAMr8{?H588MoIE_~c*$;~tq|_%B%ni{^kmAq@p0n{L z)C^JGN+1J@5ok`UPN1-ILA67Lm(Z&lD~Ji41LEQO0oa0aK)e`?oC;KzS&5dQAp(>h z!e52q988Xg0u7)M0^`V&##GME!q}xgAIsB0MQQCvw2TulIxxqTkbO*sK@F9F1a_?M z;xvQBE(9=~h2ET|VV}of(#mWO-JE4~g#{00N+1C)cke)92+Inx2`>!D8$cd^HHh>c zH8g*41KJEtZAs z^#YS>+;sZgm3aA-m%3U6w_Kn4E9oQhA5#4Hbq%Z&rbP5*IH9ku%$YHhHfu?+j}siZ zI3KmWD)8uAyj)DFiyjTRbi?O_Xq;0-%gpWrhm7!C&fjp~w^Dah zs;PDp3tB}gnhR*v*Q5YiWp}idFuqNvVph^;WJIpZ<=i-_445cNA8!<0u9`|NSM`GQ z@=_2tl;tn##c`A+@$#{uym(XpfX1K`ox1tW`sK!FDU}0FpRgD8;`G)?Lt*;3UX*X_ z8Vd6l_2R;HuA#hm)AGSwY_{4z%uCcO5a*cD^ah2-jizhiaxFv6SD$iat7w>>lEwJ(VV@eiLeJ$~)^|E}&*w6%ju~D3giKf!@`I>i@_#R^* zWVVJO_DYP8{!?E51tXF?)mKzYn)=AJrm~EUDZ;ihwPdWTB^QY8J=YvqFK}Seb2xYEs}V%wr2WM*))B_Hf7UttOGRr@|V9%(~x{SeZ$$z5+_qP zjxrQPVILxk<^g9ZM%GM0R9@Hfc^B$;LznF(pNIHQ)05E~F1Zx07H#1)~CMbTC(E0s)OA_ zHH`@4lQr7mw#u8fkcXzK=i4emj-{&jKus%#sVZ1uP0M1ev8zpyj&1Dfgaup9p_;~u zq8SF8X7+KXnW~tYY!a?$X@(-2CP=4_r}&cZXo{zsBNH zHQzQdX4A|#?hIF5?5fzI9qy^3!x3psacsh9G!2tJI8@V~p?eyU`N(#~x<)=(Yli7L z7Q!wW4E;b&YmTC8hDuS})=06jOS2wn)zaCT){ncUHLN*~%%+&^9Jgs*mJK4NnM&zw zp;GSBw4z{4L?&HW%5nL(ZcE6Jd}*qCmMPY>Wod*GM}2^(~8PQ;fOXM_}cYMID35oy*V^ZhW0ZCkZu zmrXH4;!Tq!PBt2zZ99&Okft1JoNd>zT|^Bbwj&*{vtJfgfFEe6qUlfX4uo&r5Vc0I z=n)5RNmx5reDnh~_MRI!@B@xW8_wgDp5f3#p2N16w~|g#RkBRkr)$9SkGBSpqj;n(#$wTNS*+uz$TB&Lm5+zm@rmTpq_1I9a&@e_zWp^= ztZVp+?|V#vFpsOdcrw}S(+(#Zn|oZ1%~P-?sTvcf?s3Jbr^<$bt*@ard8)6=7BlcW z#_<-GryCe+3fqs^$2IDBE>RRs^J{E-G1+%Yvdh?sbzF8r^m5XuR@nd^sl%~>4V7x< zgm){)n_|9dThuU05u3-|^7>dZG?NQT|G4YfP&C!{T*g52g=K2y)F9(;S`wBtxzoJC z-C;)VeAzq|`D**~m5;e3y>V%yM;v zR)jCr;Ty`munyl+$$jexjd7>1!#Bj-#SY($a5X!8L%EaM(ON1Tv<}~5?hbdfmX3&x zIo=(hC%!5x}fs$rAOjG~wb`#MJEHlF9r%~gHS`NO&JXLB^mMA8sK_{RzE>Mu>2NjB-P zhA*7Q=E27i9eGL&yV6jeO3PK2c6-aVeayQjBTX;rHEh-*dS$Vs*+}O6tD0Z_y1_4Q zxbk=o`KFphvDxiE{`IQi)u-#GN*Ajz3z&}H+$t@CGM8NpKt5SuSJ%y@O7}1R@5hg6 z69rF=6wo`19dKSepyUN%doSo#fT&Ej-?dRtluWU^O7M5XJ3)xtIBBP&xo_yL3D29e z$cVHDqg5C-7JRxMU#m|q&!ny6BJMTxmZ3ApO=l%$CeQRWGaF1R=V&Oo<$m;S-#>jb z^y#N>2DD7`380iU*|;dGoV{tkSmVW1pk57Xxod$B->z~=f|9mDD&~L7>dWb-kzU(o ze$M`XYnx7k7tz3F)HHZagIs^I(ajfbxFt7S5Pn6~i2>Y@X^j_dc!u1tal>#-e%MOD z^Wlhn3u)j|+L|MN)3G312dQhLwLiUd++=I8q1I4{&EMGBR@;`5z+(BWx4D*k#S)Jf722Wc_F3{kcL^fdm67GZDW(}_oB>72R8&pzMbXqMnx z{AGFbF`@-GdTO?yk&oufIEb>F$^U)vNAmce?rjpUmj9&0{gd#zf8xyjCv|}TXBasD zOe3`X_K*Mi&llf*jGoP8Y;q;|iW9MooXaFEPeRs9&vgPGbQjfHY>4{C+*!8V<#dgI zq26R<>}Yb*IS(t(1#{p9le-pPR!rZDX?DW=YD=b_oX$1#6ZPft(Z!f?;0;Q*BkkAQsr1uz zUMzPAkIY?^kH{nSEg7WV%N^U?IleoVdAQl(`>XNMc5=S{Lg<_Xe9N59*64dk5z*QE zsG)s0o*15-bLJxV4nGf)aP%3Sxrn>7VJaPQN-brzEf zThmkHLp>x>&+yxJXRVwheRpyS4)>KP!iPQ+ojq57z^||=so&JOY)%FW)q|>=+Y49t z+UB^?YPc8n^>zd;#5_~&hf-TUK^REGGa2~in|fbw?Az&iro%4?o(VT_>ZDGc#h-;K zBd3jJI64Al)PLK}f(LwT#bM-}Z@M%36{ma~InQ;*^W2UJrk?e99>($gYB_#~wPSle z3c^)Fw4N;NZ!=HQxND>7%4OQa?u`Q1mut7BFY59zp7h+bkLo?_;R~2@DXp8D-P35cFyL5?oHZ%|WXcd<6 zUB#QEMLls_$Xb$yMfvxd0$_X9c zH@s{MsZq^WMii_-6q8Gjdje)O1c1oDs04u6J~3otfQzB=JQ#VqWQgPq-WW4d(G-Qt zJ0x!tr-N5hjmzPzg3x=rqB73X471dO6w+&Y#U;DR{y|&#%ZHa^6Lc0WUYRp{hcGIl zo~=8Ot&*GS5*gKX-7t$qXl#{#H2=I*qlbCUSFvI~+y&``{4cx16Mb0|gS=XscL5V^ z(gx#uj~=l1&iNngW^ZtY>N-4^M(-H~|NH&Bx3u2hKVIq8QVgD=kX_|QU zaU=D!I=M4W=x@>|YE?lqRt;<89zSw2sfd{IEcN7ZL=)cQQo~D>phe0_|N9Cj{3|R- z=irJQ_shG4Y^V9dyfFEWMFX1WFIgv)TCG<1^z^CvafqVZ6)DeA^XJ!PTIfg3JEWLR zg|swocY%k0K@ko=(EV3h!bx`1Z%}d@MD!t*W=~bAN4_a4qJxgfI`=#}OfMN<<;1dW z9ixh-)k)$o<(+Yx1Rfn_Uh0ng$E1VGO1ie9>XJg129oepGaYAYd9pLbhNd{GZMzm$ z8RXBTxMqt^`}@PYm+zK1DviMcEnnh@FDy3lWI|9mq)CQOUWcsJsjbaq z%``Rgw%;u&zuk$0l8; zA>FkdgXUCi?|rkO_XpS5();r0Zf*f76(AMIa112dQ`Iu1sU|Dt)Nx&Z>RPg^%UG6l zO=}0KC=DnFSJ{G6*$FA3|vjVW90 z?K>YdkyuBFF5 zUDb58UojKs4MA@U^Ib`X=PF5KY}~O*e_6vJ1P+g@TQzg%2_<;GwQBUPVo$znxV;frJW?G1R z8%lw$6TWKdv^Q*b3ksAy_J*xEGN#F7NN=DmXi@h_i%i6+F55$P6pgfK<7jE!Y%xrC!{d9%Kn-R-pkJj62KQY#19g;P7ZCpa5}GHge@{r0zIw^hi*-*Tb&X_D_8D#uL$+ZUIrYnGVTm1)Pm5xY2ads}QK z%KKBd*r%|4ISX`J=m^xBIg8U-#2!#Z#$(TA-O_bar`@b^^@5G#FMJrwT1LxcWZ#6y z!67SESJ3#bNo++qo!%oMB5>ao&zMQ}BM+>b;*bo+`eB@SY(w;s4JNBB4gNtX(#uwA!|R{DqrFu zzQvJOwkVn;NlV>3yg=+7cWbFK05>n$ z(W)}E)iO6{?7ofFFM>2B^6G~b^;j&MdD<lp1HsHNU zkGIpC7cV?n^<)!cvdcC!*~Q55O zsV+s=H+%Qj{I97j)=PGmU{YV`Q4&iMip!%{reSC=%OvEiAYYkeC|6;)ZYsCfe-?;j zzH%41*y?l>T4zxeire^;=z?MFN#^XSnD##t)>LYs!nT|^%i=|yaW#$5*39f9b5d=d zIz^?TYx#_CRK>fj#Ood>JmeolFGu0rVKnYrEgxa;yQt+CwfvB3`Jdx7qXRbiz%u>~ zQ_jmmo`|rlV2G(lv z@Y^Wxl_gnG-Me-6OYaWp-Nr@S(aXx-dYSI@&!LBZ`epLH=aJ1yy15U$&Lvs{w@~YQ z(6X4@>rRwRfnT_XAKL+~GsL`%!*wsxJ;57J?DyICVfZr<(qY*`PYaMOXBMFF*Z6b& z%?Gl_3Cqt$YAKWx(<)w*{SaTJE8%1`WLr^Hs%m_lKid1AV#K-qWlwS0Q|xz7(d)Rj zjn%(WDL1s3uYORBAD^A!zeZ_AG3CzBUtx0rNnw^x`(R)j!`lD@+vQ{cl zY?Dbn|3iWr%{!eFq!}Vp$GaR9^dA(AJIEY!QqXk3d9ZVWhS@Xbs9=~kepC1FLAhU` z<;@7ks+18DzBdPLKvrK1dS*Aagbz71R zoBYo}mDMeFUJLZ)Z~R`5qA@WLi9gwtW2FBmsEfr-p&we2yOwF-t>I@Qy4oa{Qtx?I05+<$FHmU9CKnG@NJz)F)a?z)romjEa}pbz{;GzBCiS<{D{q;Mn#BI3 z+CMFtFy(Uz{`(52Ss@9<9YXdd758|#R`%}uU*Er~R`=@t{7)wXNA=KY87Km7EPKMdSBdPgK- zl!-yws4cF%l{#yB&(-646?ymnKBUVFzFq3_dF;2s?Ha|`?8u}p#AZ`-+uolhs%Kcz_t|w#^E*lr(+yS)wOh@*m{u9 z-hBV_&s3qyKO4}##Qy1Pzq@5w>;C!!7j$1|@H+;INbF%F)YUyDEv}_|+E+&UxNu^c zfBO5h;qzRrBGfRQPk^DzskW`k>%4BQ0R@TZ?x&{)2~z`@Kirf!mCI;EJl72*?#4yg zscg+mLy?sARasM+NeYp^d|~Du&A)AN!10fpbfdc6XQB)6`IN=^Bi`2fVz2ykHPqDfhA1(eMx~95$GI1I!@c3L;hVSy zH7Z2M7RJslp}QyJ=AzV#gG`#*UP47kp6V;AB~5)~T2!+&rU=_aWt7QQocw9I$eCg{ zJzThmxA^mf#nhXi$eZwlER*8X3E5V&m{MHPpRn8%d_Iuhlb_?;^eFKr=-Ry_Tj-E( zqra#7!h9BwU(xGJusq<5MyTE$R%g=qtNCm4luh#3k2d<0v7rNYq)%<{KG|rXPaS*de@FVXj{a*TQ0u>c zKZd~LO=P`&BowOwsNR29ey<~8bc{qJfkqdB`kn~X){#EO1ySh?HDI(-w@na%->CEu zg5}esQ4YO&`TjWu7VuTu@)Vy*ZL?o$AN=CJ?s>BAN<+1XF_-tZq6f>0?8sV$9@+D! zmQ=}ZAXu`M|xDva2&A!nxRm7%&CJMYwFNN=R2C>>RKN?#@7pZV9pdp-^=zuF-^-fJUTFn%Lm4B zJawwru5B5vZD1W8c6;!C<;4>>UMvV#uIvlQewJ+vN=&@GJGkr$hVKembPS_Gncs|3 zB|R%`euF;MIJ`E0Xl2xB*cNO6)xOrQKx^0(3{v&B+!E}r>Q#BlGu82Q*F?UgGS-Xw zS?}R>JXtn0Rc7=!Thg;n9nW)h%QnrO=uw`T&Y(ij8FXDXk*t$}v0cmu#*$^7D08lC z$&%q&#-VkF_kX@v5Ra=XC_{9GM#IoE(iOg*;m;Ui_-nN0>{-OKXs|y}@4Ea}6LWZ+ z3-_`0v6n2(R9UlSTd{OYRv7|*{AFr1khP+7*Zt+3dm633G^Cj>NxGtIvP6W6Dz6XD z6mw>4wjx`m{`6FfO#7Q^&rDM@4O_Ex#nv@LzgE;4J!ez&Poi)fhA8PKd@5_C9@DZk zTl`}c1dy7@JDD`lM!coZbX!%`Qea%up2?Q2>3Zn|O`NVT|5p05bJ(@9R=mC2Q@M`H zj^RG$nU~NiyPKuy-HaWNad)xWn+4A9Vt$%Pt!Ha5X3o54;<}Ici@L?IpbE+3JV@iY z%RJ6Lt?z}9w*RY$*UqDIGe_o6BkZ)B>stF-c3Q2$Ogl(7-qJ|B2i=&+&M>|ul98TW zE|%G|RggW|a2!oydWPGtXB=EXHZ8~0e3jATZAp*3SEHKj``8?+haFx)cCo8shtfmT zbWRUa@luF$-=! ze*Ad#^3UJD{nM*AujYS#_x{7%@87(l51;EMLLcHi!1b(MHxb4*TDm^SeL&Iov5nY~ zK(&Lw#vWA1XARhiK&`_+WG}|T9qH3MI?$B%p-=8epwZre=3X?)9qBVWHnRFY4*qwf z&+763YU)6%i$HT<1g^7(*ad<4hJo!DAM>y!^&A*or2`PCY!J9+(M=R~%Mkeh6sj8(s!A7yokB!b4nW~rjIhN~AyICmu~U%9 z`;sIjRi=YcH=dn#GHO}deq>6yLZy4D=m(%sX{J?L`v4?%sSV9EcIpkvc)?QgHV|ojUzUP( zNR#PArWc;*o$p)ckJ7a)Y1g{`ZGJPRdyaG{9nI6YQ|(aXy|YtQ>KK3~eyVmV9!k4V z+RBzHA|(~(R2qP266aQhXKG0`9MiE8a|cASu*?~bJas+ORCV7sv7#Q+lB#*SW>~s3 zl^t0Z1~JLTQ%N-?MRqmc^8903QYFiFEZt-D7+cb_PfM!fBOO!A_YU;z(~^oMUy@{( z(PM2%&pzEr6|5+lVK92^E$P{(J1KG$PsTQ*2W?4@vCs8|HOG-LqsQ5j9&?}LWtb`% zGfIyw3wp4tP3d)b?CLIZtX}tVYmf9;DwY+`--#Z3pRP`-Y#7+ui5_{czF>-iv0*ZT zlx+#xCx@01!ZvmICj_aSAPaeDs(QYyB0`R;;$DLGdf27;wu#vivyE*D+9!|Z3d!^> zdI)aWhrO-ulOxMESy8GZO^Ik191vlwPkqm|WW{w=GJSdp+9yl)6tbRFjS=Juf{Cp``{c=%qWhj>QG#SoEGgYrriw$4Yi-@}Ri&4peKKX+uuX%y@sS?# zw(X&Pa%J0AE!kx~WJsJKqO_7}H%v0s5q2HL)_Vz3ARKMK z{N*pxG$emc-{3`@JWiK5nbMSVD2T|fqaA7&O`hP%_8Hxt-=~wRw0B=Nb(}AodRn(_ zlp*(7#@}&Qp7FJ!4&TY~cii`UbEj@mdzo{v^x50h3R0(({jXBO+>U8R8?W;3vGFx_ zA@I@+W$7`kWNQk32xIqt`qR6!xPJ3G2(g1vHccPXFut7@Zw`FthP8INp!>Bi=(5xA z=MinkXi#T1PwBN|+|R9CZeSjgca`)qNlfEBmqM_Uzu1h}{%V3^4bhhr>2hs%@YRGm zi+QGEpVK)NA?2LMX+GDEEc_`EdDH0>Mc!1*bDSPi6A+Z}i$hcLb6YLo_+IrO6aub5 z=PCgmgIsSup4ovne6cy~OZ00EUZR7Tx4mCS`Mvw1SY7^`ohpsIUl04eBU2hZKO3Uw zDWiQq_vKirG`fCn1|U>M`+lnX?*D(I1EBv~F#w8YUMsqCt!Q)%NL%D3DWXd2BC>5? zE2?@e+X^qDvRbrKEizG0Wt;pfTfB(IYB5T+sQR_6+Si8NHPl*@gX(5z8fCP9s`ji_ zsL}2otc~_;-Iv>Fi9WO4;jxjvqg+8K(Py>$j5gADlp6>o`Z}Be8|gdB6@)c?iqz^A z+_|Qtd{Sdxv6rrt#F@v71!_;-}l(rw>Y;%gbhy(+p&>}h;EGRWA0ddEE$@~ z{k8lp-COo)$+UFcvTg3KqWJ8^%% z=V>Z%HC6UC8sp7IjM3OrTf){<_8r;6rfry(btHoU{Ym83B#tJC2I}&HhrlCU$*$$k zj%Gm^X)73P8n_EQ{0p5pAE?SuPMW+*X)?^)AW92!!xoMT>qKh5uA+!#N)tWGEgB8x zdbv=nBt>;hLs6%;LFR&HVodnEfChz>ot1kgz*<%)+jaAcDWM}FjgWxkXkKnV^sqWg2r|U!` zm7H~#N%@A`>N55Xt+LZCLwI?xwUD}wNzg=TyLsa zbalyOojFABC;#VR*))*lIUR0%N|W(#aFv}ls-Xy5x^92>9*Z%{HM{3%`9P{Mk0~jlWP)d4{`J*NasGy;hyn+ z_;?kuw6hE?d)u4~zUa7mUxh1|v{vgZ;Z8D(c#rU)gW-1~DRQ2tTb4A#FNbv82raPA zcfC$zO+NijJTG#c64F;RV<~mX!wF9a{oC(NfJD$|Qqa9aNj9$}vFFc={I|)igXp=Z zZd@xwt(Cvcdm=2kN>)45dCd3JRg`is%5@&#pCX4w*`Dyngsk~zk!6=`bvqMz*LppO zW^ME+z4R0vvp`>j*1T?La4n)$Uz2^J_)EiE^~f+j5Weuh2W1rn8SnITgP6gU3u1^K zrP>zmw82-2;WOv~&*Lb3tkRQHvx?kej-{(bxjn_xRh8P16<5h^?AVm2s%$78vUF8a z{c3xB4$sM|7J1cKI^B7Z?OGBueTfOn$8$Q%^Yrw~Fv#X?3eOiTg7yvFs~|5ZqI9zo z#XB-_7kq-G8FKHjC({0k#lT+ATW46|70ZO*oJNHXnZ1M*{rz|L@agGAK4g1XjfyT^ zYtpr%>)S0y-*G&hsnLU%*~GeSqRxmh ze7-UHF1DytLe1!8p8U&yOz7RD$-n>mr2P2*ok)}atUUQ{GLhI#u^AO+Sh*l*#G}k`143(7TbAy+umveb2rYHocaPEAK)+=Q(Ptmn3?29s9dey7l&j zT4p24s=c*KcFuZ*aWg0EiAj(1Vsm$!l~hE2X1&>@qRZE^zS*Sj%ATt2+~mw`Y*d!7HTznT zYmG`+ku*i?T+>4uwRFa0d9Mc%;j_i|l5tf2g6;XLBa~*3f~DP|&@a8Hd@iVUF;;v} z>F=UVW1G-~r$;in{lTz)cqvKcy}dNkH+J@=*3t zbe57@W~&r?v!&ZakESMx_5ROrB$oyP zw0jyqA+fSyo)rk?UW5$>V=?o#5-puJV?mVCOH`-LcyKNG7Thz)P#e_eRfv&$OB_cGz-u~AhrAl#hX}2{vxRz?G^6qP}jTCwC3bZ|> zc;0?j;QDB0Xzi6nOT$;7Fr3#GFZIZE6~Dii)8JMZ#h|X`eQWr+;kByb4^qC4rRa~0 zA%9WyPfOANIY!>(PjsURRmJx)_L0MRq13J7w;Av9ntadJENgg8tp=%4)+gU{T|@3` z_JpuaD)g&Yz&h!QXlwJbP%GT7w9F8V}#-*LPh@jIFLBHg| z^GcVuPEr;n4WgrX+JE+geaCjhG;hoHH1zDJp;<*I&1%L*v$E;Cj-_<2uHolDO%1o4 z0m+_c$db8p!{Q9cONhkr;?L21m4xeENd15=?`+R^v7`2P_6Z;92#j(s#bQUk?QC&_C<)FtB*ds%PUIg}@ z1$Ljr{V~7FHPXp#e^S5qSSQCvEELFsG^z5eaw zpOfF-ue^BT#)}1tytMkEVw#p|Ft=mHhdH!B=_gPkdf2V;vJZ+>2#)H0Jue9Diw_Z0Ywa)yPZn?IKs_#yi(8(3jV1 zF=H|@6GWv)vc;&O8Y5btTn5o?h{YWn6t2~Blx(=-^v{3>C)ry!j91=4*s|d`iha2n zak(0C=&KR*q;NuaDCflD@E5{sJTmU=aIR>pb%Vv&&ZvZql~Rp-t`&aNBPZ?uW)`(fJ(|avkKpQB>%ifR?&Bp@8AA7 z8B&-$A70qQ55o&n6Y=oU4=5R4*oot;;wo(WCe|#}KhL*2t_adML^OuQ=68LE@Hw1g z2=H<*_?~kN!q~`L5qpB2H(WOyZj{EQ=~jv$M(O7`ExJ{HdQe+ErSp!8vHy#j zuSB@5Xv2!mRGA-fin0|IjHOO&H4Lm(nCd*zuk*?BA@7CvcgQ2t_hpc{A)atcMVfz_ zPbnIgxUd)%Lz(MSB4L5(rHsr3XH^jN1})PG(JCJT+Klr!`OEu1y)VV(VugYQS;a4w z!YwdFk9-Jd^RUk z970HYhVE&)G)1;6?z1AF$PL5v91CIBQHZv8F`#@kmxT|(@++D?I^7k%`|iU}zubKH z-Q+_&NpVO}Pf!$RcQ|1=Rjwzu@g$2`%J$zxy7oM7chxIEz-!7T`)w&ZGj%HP;uYa6 zQ#TCU`w3$0OsF0%n5 z4RflPie^|ex11~qb!o4bHqpExyGS>EX{s5vm&+`PCepx~DIuRa;f*&6JvW0;X$0>A z&%;rPnrcVLy&bW>KC+eHtI&963FiDLon>Uor!%)mnNcN3=_P2O`z!iNW}X~ymly0X ztlJre1{irpFri}^d^As|%b)p{>zDf~-~yI$9gba&I>FPp{KlK17#iZ)6tri30fHX@ zk`1{c8;!B6rH0tm4R;I)U{l8ZfY(AoUNl9mR)<8bW>*a5hW6#&_Mg!e!|Ow50T?!P zva2du4@2F!9=hAm=?(46z3ul6-CpBLA946>>smI=Ek{pPEEAjBn9-AW9X?sFQD8}U z!2Ee;#PrjkJfjuN+9*+aDe2C~GqjdmlhbvFns$b_ zIwl{_(8}{Ja2>)lrM>0j`N}fysM&C80g=b80$I~!Me{nPRc#e~PJ+CWEtdhSVb(^A z+)GR6Iv89_wN+(!tAo?v@S3&Wnp@J~wC1~!woP_37;6=Q+FO1z+06;8z#}Q9Lo|kC zDW|$D8yfXA_L1UrTqC}Cab3K)o{1ON%8O?>N#f+@yCG&xdWD`9PcOH+PU}`z^QfQA z4X=_5*MTm0mDk2m?{(>lg0W$C#$LUcL{8Quk|(RZMA^SY**|KO{o2IaDZqDAqU1}A z{iDa&cQpyimVb${e}*yko0i}u#Qr73{w2hIONjj;ir3LBOSXgzi44B(S*EbyTNbr_ zI-09GmMvlJR4hw{#&j_*?RVpGTr<*ut;<`sJD`!TmPPc@vD=qY(qysWe@S_fcqC1x z6PZe5eqckrh$QV=*J)71-&W|1cAmzH}`nr)J<*n5}Ojpha{AI13l(m6?NLHavr8pn9{KFjo zdn()W=TE)K0Gbwji?@E6-1t zB@d~dwYO^qeW%&jMaYw6dYa9%_RteCQ=Y1_p?JvBRY~>79D(iChx&9*w8cdS=~|N- zuTBqKk-p=2x~WJbR%D*Udgmoc^T#)=NlKE5RcW_1Ik=W;tMVS#WRZpu>$ZtHb)Bv& z5C8HXlV}x&lYjsB$)KAMQEq+T;xfeGUdXZ8n&cp_AKT_-h(12yW8HT=_q_SUr^8&o zctf7L4o2C{q+L}z;z7A-Ii}{bP-3ok$vbvAB|pv;j3JS2cS-zJIt6!2$KT((E?#DW zXZ%5ZZCK*$k;mlak6+uis=Z>5*Ri_Db4@c}{qXW=2jv&r)X>`*dF15F3B~?SC^pyM zQLR*_>p8MY#FFOaH@1;%8&kt}Y(z9VMMCz)f9vAE^}PJI{v4-dp%m+ap6tE+9J&5&GM(uERycAgnn^aJ%C(~Dd^;+$5^|}CEF0Hk4PDE!C5q^@h$5|vsAM;* z+Si873$XWc>Ah^**NUo=YLu_(F;;YHbG8w8xtg)V3zlYNfE?wKiOxle)_ar9+FDQ2 z;K$pY{V38np*lc@)@5a*Yh7A$s(UMp$v4`jpB-(J_tl@{hl*Hk^Nj0~zwNGWnAcQy z6k7}2iYO%;LKcHP-k9J)Ty2ZRx8!nsX&d)t519hSh$e!G}oQ9?htTnk2QK+~I zXn5GvX_LCPeUl%Aci$tM?rW z{nCrd_kvsvYo2Rb{lfw=!yN^WvE2`TeZEq#jXh?*OUa_kW*NPj$n4>>qW&tKa{t=d zXY#x29T2demE~_z2WvcFS?n7^9vfbjG}lG_1Z*Dk(BXI?eluW``Uw~9pw6S4!6TSc z%TBG;erk)O8$M@h=WK&}T{LT|j^*m|h-Sq=H^tAAPb^~V`1FU37dE;Ti(?(ySSpqk zk6&$-FA+?jX|HsP#8YhkiOxX?L0xy$FE#8duIhA7_^tKJ=5*B9&kU7H-IUt-tL zbxGT~(PQbCu8kF4?9;==aHeJDY8|QDhkNN@~CFqIH?BFuv8PNlDkz4xx#6g2(@&}+Q>xIci-wz(aB9gzOYrfTAzG#;B z(?-LC2R5Gw4kG%iKce9XhBDfzVh)l1M^iL)nsAM04{$Hp10W_F>S~u}XGUwoE$SwcCC{W8 zd3S7BsGD9wG%K3&be?BDp3PpMuZ>-s8uoC;Ib$efw=!ehF2^I`UCJ_7m1! z1xC3SDB6nF%(3}r!Ar-3o`8|*(qm;>DZ2HnojtM);XK2 za$QqgjJU64#}}uZ&xIoNbkvWqEY`bb&E9iM2nyw13ZJX(v@O&^!*+5Q)+5+Ux8Mv% zlS!^&yDY$m?MSC#GJRgxU#|~O6_;ASNuOp5`u=i9w<5{OAOD()YiPeyCK4xtaV?kc z`ST~vGm#L7VSp&NqE0u|1{$RKoyPSYz3s02HVqV8$oKfMypD0{^e4i%oc6r&vg9tM z2jzCDTDbV-U4qi;IccqENyj0A%e@mnYjm`<)zs7z`R7?)i~bd7t0bDdByuI4yz?io zz64|nuMK*~IV9NW#_@0n;>o9BDJ!-L;IWw^1Ak$X-NWKiaheh-_QQ(qtxEH^@_dc$ z4SziRrf;0xktg-qOHbEzzoQSkLT)r(aNhBLutyYth)xXgnX?j+&ba>R(k^4_B-M(@ z^!QL~x6mf}=VBbzFUur}mnE%zNH2GE)l)UF2w&3q+_|aJFW2Z7)2W&MoWy$lEEs0` zV;rrrbr+Uz7l$a#h{pAROW}X&_kfdCkkgHo<=|YIEc`|+c^xEBUFnfm7oiaHZ;}Pq|oseKO4DLuf?U$*F>JLLxa{F)Q54F8hScp z2NlIHfBDOld1_o_@kJItS6RH5a9}4Xj5%_#N9q$I?s7%8IoXU_kN6d>Su{-MtpieT zFOw;$e_zEJ8cOTT_0t*qg3!7rGS5vk(@xLsk~qu4e49L^=lKi~A7XER(?lOc@jc6o@yz;{xBByL_UG$%e{_|Y*AK|#m<|#B4s9$3Lr9Eb8 zO;`xebMl|6_xWCS{c^uFeuKVrMOf{lmdLC9Qrs`yC?{rDCekq^b78zBAJ6lTk7W3v z1gDcAns7?CBRZW2(a}snoZ@(u(R|grQ5~=L6_{6h9%{0*Cq21e>-;G~F3o&1Av_;% zG1mj}{hWTk*G1Z|g8pIh2K`T*OqRPW)&;F%zW*g_orDJ6$tC-_4$X71O2*9_%GUkj zyZ!KUso!}2@|TIT@_m+WVK}k(Sg_Di8Z(|{d}#f_iWNt({@R(qz|>?QgA zXSAHSh%~ev&HL?QeHgu?RTHu4Cg1Lt-hQ4ZY~%+&<^i;|?%gCDXLn5+4CC7=OD&U5 zoyRE+4nP_Fscf(X06x>EEJXAr3;I#Bpz{%!@F_$+jPogU$%TGe`KDN0kbLkid8S6^ znM&_ag8%z_ei=cKQb~pJDBk`4R7*tFx`K|Hk4mu8yzz93B5x{WH%>1C;UW-534~%E zO~^c&6!Ykuq{5+iYo|l;w(Nhe+xuRZ=WY0ZozHK2^lq|uC(g)Lg_Bz6&y8HTZQ5?I z;={Pz@^vwNiyneo_T+LU_hhf+^0Az($MP%Q!u83{^S!t`sZeEyC{i?xA;a>Up!D4X z^*G0$KmD;B$57Cmi_};Yq7(Dm$>g7t-)JzlmT!l~K>T*{&yOFY--`WlI*A+WqYiLs zNdC-EgO@m&y2TMOt*O4}9ySVfUxd7O_P)T8Dp@8CNaEX;f4TB_IoW>pC)>ZSqKIA? zKB>EG?KH11uU&TpNaI@25Y3RVr63{`CVT9;&DFVOtzGA;rD3XG=jyolRJ-?7NnaSG z^P-tNjbZuHRb$G@Nfu9j6?@@YmPtBu6RKM=qNoXjUW{T_ql!jF&KYrMvpM%<=^B=} za+ND*oI3W!n|yQrHouzRo|aOicc8c8ZeNP13i(Ut_BD^F@bT&oQXXSrEgKCfr>Op6 zx^h0?FIm25u32Du))#3x?PO0ZnBKM25>RIEQpiy}nFhm^dhH_a#58 zmXM^`Eb)qLpIY$hTt@YVuNElfUX<&YEro~qz9L!+8kAld`l7OYyplxU9yWGY@p}&; z*qVdKj~zzaR*~zg(uiTq6O3#*kj5f*RrgyGf9jcMpKh%?#D617$kq+dKAgG^+6OA$mctV zvT0Pakc=Hf1^cerAxNrNN((=NL&>`Zbqg(H3oaLHhSb< zdcbaUrE6I?t`$YwZa__4MTTUJ8PJ#Xa?-&@R1h?JM6Wve!N>c^uaxygS92!Hk5v_MS5^f z=EmR*?)bc6Uqz1Tk7#(6atj2Ebw2paZexlI;Ute(-LDh8)8h8?@2eyzC&;0XWWefG zgpOsya&@e_zJ1BMa}HT|-mS9R7;@Gf+jA7j!6UNn*skXwTLzqUMxPhqODxupT|_&LGxsG@)epicfc(nkSngQJ-uZQ^R&_WFo@Ek$s7-x7W>CD$ToT(wMd91TLg-7;N zzeTHC;ag2*FuMd@{)VRHeJ}j4$-XDwN$=GNcwE6LRAa)8?`COwH)ByG zZoYS2yv&%zrPpU+#ZF=URXBP7{?8LKQ-WyZE&Y;YbxR_3O$1|XuMJD^HX5NVfBf3E zRqYjf{8L0$r4WvP&@}Va4=;s?OjDxwix)qVgDl4!@^!7GR_nvZ+$ZI!s8op`BmeV=8tENuFaSffit9E{I%BD_%Q0_&zHg%RK z(ZSBQ1zk!nT}QKTcASNABj;g`Fb9pIWBJ&y2F%m2nq^MJtlVW3vm#qvl@|GwM()!t zrsC#n;mxe}h!$Z_l??+kmlf`6Z%!TTYEz_R8@sxT9P4sS3B)h(Pt@*mibs=07g5Z) zS+7BY=SIr>+LEMI@1W(^fFw^>6x=!I`R;_V6~T3Hvueh3iE;8;lZsB7)QpWL9Zg4` zBTweP15Xv}@D4 z*6j^nb7_b-sz5IHB410At)xeOH7~)Ob);)Q@KO$v4Z4(Gx(3P;G)R^tD2N<-Ou8?R zqiQ&~W{;!cP}#SI>#g45%(|6W8|oZ8iq9NPuIUEG8olgIw*CCH70I4IMW*F=rY>oo zYMshyt0k$DD?1E}DX?JMnOZW|Rr04rRH;i;^5aG&e~Uk}+{_}tKAGt}g9}{&`#9`; z;r##YU0HM5$QFLTzk>2SOyY__8xo=_b*~wmggACGvEwAJq^Op zg+6I$JJhwqO4a?IsPq{>GbH%vB0IjD`dpHwCw+%x%K(H;vSXW>H=VuQy@g5BtKFBG z(3~Y4w5IIn&}erwRVBbG>I_(Q9ZH<28Cpj%GzGP{CL~8!Oi2`IYHYDxJ(v2BSi>Q4 z;0rINn;g{n0);(&MXPUJ=Ux#>; z=LLh_V(rxFES{S|j8N96#S1LGd4uF7dp39K7XMbesuUgxf{vEb`6{j|G9&0&lL>j; zVirlfVCXs)V?)M{0&R^3sps=Y(EKNqvdGm+$oE zC|R4&)I_`@tL*7lv%c#09DSzmXzHZiJdW^v@fm>$@szy$Exo6bB*_|N3HFV9cu!vy zcUyMZPt#Or$nt%=Z7QW5mt6P*0uR;b;lulM99{U!JB*vn7I57bqWo>)pOrse|0oRp>mE7e?>6rTCD7PQytccq>07e>eXkM zzRe-65b!ro6XL*u7VF#4H`62UVL>!l~qm=M9lF#;$=a89El>bU{Fy)eij+> z7BfoBl8Kw?8IY<)ncvdg7iZy0*I8(+nWl7JP0KC(`y7#2POScW^P%NCm|-Ehnbyx& zE6GqJD?HpB zS-4j>{+>9aSc6dP)D0?3T?xT3$ATGa<89>hPWx=(B1_3G^Ys!{&;!)t9;+FmAggjmQe>6tQ$E%(0kgJXH6qf-xy6?e z+lJA)N9qH9~Vz_c{AI$o(wQO;Jspt`}xf^?pjG!~>|uJ!K2Z;dQW*UCf7 zLIKVR(qF47oXQKzmLuwdD_^+-3MPlw@zp zrjBg|4hcUUMgbXF1|?gWRbQyZzL+y(w24Q{#<`QJ;k&?K8xjpHQI3nofaXz+*CaI_ z9s!L9;`03{9GZtNt%&BtB!2`T2uU~ODP!vrrM%C}DNg2_YDYUOYN5|51yka`b3{QQ zjjYNJWN?~z4|V_(itph}te=L5z_6Pfd*?SYrjz*p>e3Gog>rWpJ z)a%2Gx%ozae{t@b`t#H40xp#Fvo`3AUeY@X!gF3=#ul4n6aD5`b zKkV;=x%ioX|HXXMKb@VSy>D>xeWnlo4uVP0w@xSDrz2GX@}cqpz5gNtYkYM5?Ib)5 zE)Ja5Uwf}#=V$qGW@CvPPZg0sYs*O1zy#xmQ&#LFBlN6bevD7yRCo%h$G~H+h>YZ} zz&G29dREY&H!tb<21&AL69x#MoVb9+Fr`c2z{b3YnPLFTX&}az{VNxrYYh^Ckp>SE z;E_Gs$yw=m86^3c0yRCfCXhtlPaI=>!uW#*QvD}$93~LCcnr86ogE8TzeN7hewlnx zQO9$pq(9r4agxGk;Mkht#Ul%zU)BUmW{!N|PPtt!@PO?zuG`HX{UC%^pyQ3^az)67 z=Xe|P1$Z*3xCsGuRPwo*W#dC8Dl1)tST3S`O@yKJDoiW`**?lhgM_lc)p3LoUGkt} z_9)8=f{FDOrrDuJuttz)2way0ppc5AAaJ{_7A74qA;!RNTM>C0k`nV>a$djE;<*zP z(wY+nZWs&}h)Vk8BDCZ3$0lw;MMOHBMXX9=w~?+>qDo{J+q3-fvEg`RWNp5<=n&5j zGtBbuVAfz@-R6O$s*27DvJ5%dFilP~X}q5*As)7&4CI|$TOv`DRFIwp3$~Vq%G1y1 zR?jDyn#sCTrD(QOSh&gzf)N3`K6+qlvTz@R z2tgW%0v0{YA^RMYFGu!-izKwJC;&+#Iq@G@DXLhI>XF?=pe!W;^5H#Fg%SBJB;iH# z#K$rJ4&woptGv}ST$v7prY32RLkCP}>WU&lx(qjn3>L~ zsoUiQbq?G-84>ItFHD9WqB3@Q@nPf`EUfdvamGC?M)g5ZQX_hTuS}3vnmjM11o=dx zo33|!@(+S2X{V2DF?%Kk*Yc&n^#rjgO2t@vqi7fRK#QZ Date: Mon, 6 Jul 2020 15:38:21 -0400 Subject: [PATCH 17/21] change user facing text Data streams to datasets (#70840) --- .../ingest_manager/constants/page_paths.ts | 4 ++-- .../ingest_manager/hooks/use_breadcrumbs.tsx | 2 +- .../applications/ingest_manager/layouts/default.tsx | 2 +- .../sections/data_stream/list_page/index.tsx | 10 +++++----- .../overview/components/datastream_section.tsx | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts index 9881d5e40d8a..9f1088a94aa9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts @@ -53,7 +53,7 @@ export const PAGE_ROUTING_PATHS = { fleet_agent_details_events: '/fleet/agents/:agentId', fleet_agent_details_details: '/fleet/agents/:agentId/details', fleet_enrollment_tokens: '/fleet/enrollment-tokens', - data_streams: '/data-streams', + data_streams: '/datasets', }; export const pagePathGetters: { @@ -80,5 +80,5 @@ export const pagePathGetters: { fleet_agent_details: ({ agentId, tabId }) => `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}`, fleet_enrollment_tokens: () => '/fleet/enrollment-tokens', - data_streams: () => '/data-streams', + data_streams: () => '/datasets', }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx index 2b92987963ef..293638cff50b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx @@ -207,7 +207,7 @@ const breadcrumbGetters: { BASE_BREADCRUMB, { text: i18n.translate('xpack.ingestManager.breadcrumbs.datastreamsPageTitle', { - defaultMessage: 'Data streams', + defaultMessage: 'Datasets', }), }, ], diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx index 5e0cba7383e9..1f356301b714 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx @@ -103,7 +103,7 @@ export const DefaultLayout: React.FunctionComponent = ({ diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx index e1583d2e426b..a6e458a4615c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx @@ -32,7 +32,7 @@ const DataStreamListPageLayout: React.FunctionComponent = ({ children }) => (

@@ -177,7 +177,7 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => {

} @@ -220,14 +220,14 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => { isLoading ? ( ) : dataStreamsData && !dataStreamsData.data_streams.length ? ( emptyPrompt ) : ( ) } @@ -257,7 +257,7 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => { placeholder: i18n.translate( 'xpack.ingestManager.dataStreamList.searchPlaceholderTitle', { - defaultMessage: 'Filter data streams', + defaultMessage: 'Filter datasets', } ), incremental: true, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx index 87906afb4122..eab6cf087e12 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx @@ -51,14 +51,14 @@ export const OverviewDatastreamSection: React.FC = () => {

@@ -70,7 +70,7 @@ export const OverviewDatastreamSection: React.FC = () => { From 984ea0700ee8b84e69f626792e1dd913607307c9 Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Mon, 6 Jul 2020 15:46:30 -0400 Subject: [PATCH 18/21] [Ingest Manager ] prepend kibana asset ids with package name (#70502) * prepend asset ids with package name * fix type * cleanup Co-authored-by: Elastic Machine --- .../services/epm/kibana/assets/install.ts | 119 ++++++++++++++++ .../tests/__snapshots__/install.test.ts.snap | 133 ++++++++++++++++++ .../epm/kibana/assets/tests/dashboard.json | 129 +++++++++++++++++ .../epm/kibana/assets/tests/install.test.ts | 35 +++++ .../services/epm/packages/get_objects.ts | 32 ----- .../server/services/epm/packages/index.ts | 2 +- .../server/services/epm/packages/install.ts | 58 +------- 7 files changed, 419 insertions(+), 89 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts create mode 100644 x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/__snapshots__/install.test.ts.snap create mode 100644 x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/dashboard.json create mode 100644 x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/install.test.ts delete mode 100644 x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts 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 new file mode 100644 index 000000000000..ae6493d4716e --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts @@ -0,0 +1,119 @@ +/* + * 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 { + SavedObject, + SavedObjectsBulkCreateObject, + SavedObjectsClientContract, +} from 'src/core/server'; +import * as Registry from '../../registry'; +import { AssetType, KibanaAssetType, AssetReference } from '../../../../types'; + +type SavedObjectToBe = Required & { type: AssetType }; +export type ArchiveAsset = Pick< + SavedObject, + 'id' | 'attributes' | 'migrationVersion' | 'references' +> & { + type: AssetType; +}; + +export async function getKibanaAsset(key: string) { + const buffer = Registry.getAsset(key); + + // cache values are buffers. convert to string / JSON + return JSON.parse(buffer.toString('utf8')); +} + +export function createSavedObjectKibanaAsset( + jsonAsset: ArchiveAsset, + pkgName: string +): SavedObjectToBe { + // convert that to an object + const asset = changeAssetIds(jsonAsset, pkgName); + + return { + type: asset.type, + id: asset.id, + attributes: asset.attributes, + references: asset.references || [], + migrationVersion: asset.migrationVersion || {}, + }; +} + +// modifies id property and the id property of references objects (not index-pattern) +// to be prepended with the package name to distinguish assets from Beats modules' assets +export const changeAssetIds = (asset: ArchiveAsset, pkgName: string): ArchiveAsset => { + const references = asset.references.map((ref) => { + if (ref.type === KibanaAssetType.indexPattern) return ref; + const id = getAssetId(ref.id, pkgName); + return { ...ref, id }; + }); + return { + ...asset, + id: getAssetId(asset.id, pkgName), + references, + }; +}; + +export const getAssetId = (id: string, pkgName: string) => { + return `${pkgName}-${id}`; +}; + +// TODO: make it an exhaustive list +// e.g. switch statement with cases for each enum key returning `never` for default case +export async function installKibanaAssets(options: { + savedObjectsClient: SavedObjectsClientContract; + pkgName: string; + paths: string[]; +}) { + const { savedObjectsClient, paths, pkgName } = options; + + // Only install Kibana assets during package installation. + const kibanaAssetTypes = Object.values(KibanaAssetType); + const installationPromises = kibanaAssetTypes.map((assetType) => + installKibanaSavedObjects({ savedObjectsClient, assetType, paths, pkgName }) + ); + + // installKibanaSavedObjects returns AssetReference[], so .map creates AssetReference[][] + // call .flat to flatten into one dimensional array + return Promise.all(installationPromises).then((results) => results.flat()); +} + +async function installKibanaSavedObjects({ + savedObjectsClient, + assetType, + paths, + pkgName, +}: { + savedObjectsClient: SavedObjectsClientContract; + assetType: KibanaAssetType; + paths: string[]; + pkgName: string; +}) { + const isSameType = (path: string) => assetType === Registry.pathParts(path).type; + const pathsOfType = paths.filter((path) => isSameType(path)); + const kibanaAssets = await Promise.all(pathsOfType.map((path) => getKibanaAsset(path))); + const toBeSavedObjects = await Promise.all( + kibanaAssets.map((asset) => createSavedObjectKibanaAsset(asset, pkgName)) + ); + + if (toBeSavedObjects.length === 0) { + return []; + } else { + const createResults = await savedObjectsClient.bulkCreate(toBeSavedObjects, { + overwrite: true, + }); + const createdObjects = createResults.saved_objects; + const installed = createdObjects.map(toAssetReference); + return installed; + } +} + +function toAssetReference({ id, type }: SavedObject) { + const reference: AssetReference = { id, type: type as KibanaAssetType }; + + return reference; +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/__snapshots__/install.test.ts.snap b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/__snapshots__/install.test.ts.snap new file mode 100644 index 000000000000..638ed4b6118c --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/__snapshots__/install.test.ts.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`a kibana asset id and its reference ids are appended with package name changeAssetIds output matches snapshot: dashboard.json 1`] = ` +{ + "attributes": { + "description": "Overview dashboard for the Nginx integration in Metrics", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "highlightAll": true, + "query": { + "language": "kuery", + "query": "" + }, + "version": true + } + }, + "optionsJSON": { + "darkTheme": false, + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "1", + "w": 24, + "x": 24, + "y": 0 + }, + "panelIndex": "1", + "panelRefName": "panel_0", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "2", + "w": 24, + "x": 24, + "y": 12 + }, + "panelIndex": "2", + "panelRefName": "panel_1", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "3", + "w": 24, + "x": 0, + "y": 12 + }, + "panelIndex": "3", + "panelRefName": "panel_2", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "4", + "w": 24, + "x": 0, + "y": 0 + }, + "panelIndex": "4", + "panelRefName": "panel_3", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "5", + "w": 48, + "x": 0, + "y": 24 + }, + "panelIndex": "5", + "panelRefName": "panel_4", + "version": "7.3.0" + } + ], + "timeRestore": false, + "title": "[Metrics Nginx] Overview ECS", + "version": 1 + }, + "id": "nginx-023d2930-f1a5-11e7-a9ef-93c69af7b129-ecs", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "metrics-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + }, + { + "id": "nginx-555df8a0-f1a1-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_0", + "type": "search" + }, + { + "id": "nginx-a1d92240-f1a1-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_1", + "type": "map" + }, + { + "id": "nginx-d763a570-f1a1-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_2", + "type": "dashboard" + }, + { + "id": "nginx-47a8e0f0-f1a4-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "nginx-dcbffe30-f1a4-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_4", + "type": "visualization" + } + ], + "type": "dashboard" +} +`; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/dashboard.json b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/dashboard.json new file mode 100644 index 000000000000..e28a61ae5e18 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/dashboard.json @@ -0,0 +1,129 @@ +{ + "attributes": { + "description": "Overview dashboard for the Nginx integration in Metrics", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "highlightAll": true, + "query": { + "language": "kuery", + "query": "" + }, + "version": true + } + }, + "optionsJSON": { + "darkTheme": false, + "hidePanelTitles": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "1", + "w": 24, + "x": 24, + "y": 0 + }, + "panelIndex": "1", + "panelRefName": "panel_0", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "2", + "w": 24, + "x": 24, + "y": 12 + }, + "panelIndex": "2", + "panelRefName": "panel_1", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "3", + "w": 24, + "x": 0, + "y": 12 + }, + "panelIndex": "3", + "panelRefName": "panel_2", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "4", + "w": 24, + "x": 0, + "y": 0 + }, + "panelIndex": "4", + "panelRefName": "panel_3", + "version": "7.3.0" + }, + { + "embeddableConfig": {}, + "gridData": { + "h": 12, + "i": "5", + "w": 48, + "x": 0, + "y": 24 + }, + "panelIndex": "5", + "panelRefName": "panel_4", + "version": "7.3.0" + } + ], + "timeRestore": false, + "title": "[Metrics Nginx] Overview ECS", + "version": 1 + }, + "id": "023d2930-f1a5-11e7-a9ef-93c69af7b129-ecs", + "migrationVersion": { + "dashboard": "7.3.0" + }, + "references": [ + { + "id": "metrics-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + }, + { + "id": "555df8a0-f1a1-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_0", + "type": "search" + }, + { + "id": "a1d92240-f1a1-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_1", + "type": "map" + }, + { + "id": "d763a570-f1a1-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_2", + "type": "dashboard" + }, + { + "id": "47a8e0f0-f1a4-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_3", + "type": "visualization" + }, + { + "id": "dcbffe30-f1a4-11e7-a9ef-93c69af7b129-ecs", + "name": "panel_4", + "type": "visualization" + } + ], + "type": "dashboard" +} \ No newline at end of file diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/install.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/install.test.ts new file mode 100644 index 000000000000..f9bc4cdbf203 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/tests/install.test.ts @@ -0,0 +1,35 @@ +/* + * 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 { readFileSync } from 'fs'; +import path from 'path'; +import { getAssetId, changeAssetIds } from '../install'; + +expect.addSnapshotSerializer({ + print(val) { + return JSON.stringify(val, null, 2); + }, + + test(val) { + return val; + }, +}); + +describe('a kibana asset id and its reference ids are appended with package name', () => { + const assetPath = path.join(__dirname, './dashboard.json'); + const kibanaAsset = JSON.parse(readFileSync(assetPath, 'utf-8')); + const pkgName = 'nginx'; + const modifiedAssetObject = changeAssetIds(kibanaAsset, pkgName); + + test('changeAssetIds output matches snapshot', () => { + expect(modifiedAssetObject).toMatchSnapshot(path.basename(assetPath)); + }); + + test('getAssetId', () => { + const id = '47a8e0f0-f1a4-11e7-a9ef-93c69af7b129-ecs'; + expect(getAssetId(id, pkgName)).toBe(`${pkgName}-${id}`); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts deleted file mode 100644 index b623295c5e06..000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SavedObject, SavedObjectsBulkCreateObject } from 'src/core/server'; -import { AssetType } from '../../../types'; -import * as Registry from '../registry'; - -type ArchiveAsset = Pick; -type SavedObjectToBe = Required & { type: AssetType }; - -export async function getObject(key: string) { - const buffer = Registry.getAsset(key); - - // cache values are buffers. convert to string / JSON - const json = buffer.toString('utf8'); - // convert that to an object - const asset: ArchiveAsset = JSON.parse(json); - - const { type, file } = Registry.pathParts(key); - const savedObject: SavedObjectToBe = { - type, - id: file.replace('.json', ''), - attributes: asset.attributes, - references: asset.references || [], - migrationVersion: asset.migrationVersion || {}, - }; - - return savedObject; -} 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 b79f9178ad6a..53ffd5c6e703 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 @@ -23,7 +23,7 @@ export { SearchParams, } from './get'; -export { installKibanaAssets, installPackage, ensureInstalledPackage } from './install'; +export { installPackage, ensureInstalledPackage } from './install'; export { removeInstallation } from './remove'; type RequiredPackage = 'system' | 'endpoint'; 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 910283549abd..8f73bc9a0276 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 @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsClientContract } from 'src/core/server'; import Boom from 'boom'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import { AssetReference, Installation, - KibanaAssetType, CallESAsCurrentUser, DefaultPackages, ElasticsearchAssetType, @@ -18,7 +17,7 @@ import { } from '../../../types'; import { installIndexPatterns } from '../kibana/index_pattern/install'; import * as Registry from '../registry'; -import { getObject } from './get_objects'; +import { installKibanaAssets } from '../kibana/assets/install'; import { getInstallation, getInstallationObject, isRequiredPackage } from './index'; import { installTemplates } from '../elasticsearch/template/install'; import { generateESIndexPatterns } from '../elasticsearch/template/template'; @@ -121,7 +120,6 @@ export async function installPackage(options: { installKibanaAssets({ savedObjectsClient, pkgName, - pkgVersion, paths, }), installPipelines(registryPackageInfo, paths, callCluster), @@ -185,27 +183,6 @@ export async function installPackage(options: { }); } -// TODO: make it an exhaustive list -// e.g. switch statement with cases for each enum key returning `never` for default case -export async function installKibanaAssets(options: { - savedObjectsClient: SavedObjectsClientContract; - pkgName: string; - pkgVersion: string; - paths: string[]; -}) { - const { savedObjectsClient, paths } = options; - - // Only install Kibana assets during package installation. - const kibanaAssetTypes = Object.values(KibanaAssetType); - const installationPromises = kibanaAssetTypes.map(async (assetType) => - installKibanaSavedObjects({ savedObjectsClient, assetType, paths }) - ); - - // installKibanaSavedObjects returns AssetReference[], so .map creates AssetReference[][] - // call .flat to flatten into one dimensional array - return Promise.all(installationPromises).then((results) => results.flat()); -} - export async function saveInstallationReferences(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; @@ -240,34 +217,3 @@ export async function saveInstallationReferences(options: { return toSaveAssetRefs; } - -async function installKibanaSavedObjects({ - savedObjectsClient, - assetType, - paths, -}: { - savedObjectsClient: SavedObjectsClientContract; - assetType: KibanaAssetType; - paths: string[]; -}) { - const isSameType = (path: string) => assetType === Registry.pathParts(path).type; - const pathsOfType = paths.filter((path) => isSameType(path)); - const toBeSavedObjects = await Promise.all(pathsOfType.map(getObject)); - - if (toBeSavedObjects.length === 0) { - return []; - } else { - const createResults = await savedObjectsClient.bulkCreate(toBeSavedObjects, { - overwrite: true, - }); - const createdObjects = createResults.saved_objects; - const installed = createdObjects.map(toAssetReference); - return installed; - } -} - -function toAssetReference({ id, type }: SavedObject) { - const reference: AssetReference = { id, type: type as KibanaAssetType }; - - return reference; -} From ee0653658d17a62242c56adfac5c59fede2f4d66 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Mon, 6 Jul 2020 15:49:14 -0400 Subject: [PATCH 19/21] Remove the legacy Ingest Manager plugin. (#65534) The last thing we were using from it was configuring a static assets directory (which is only use for the EPM Integrations header graphic). This is now provided by platform and is not configurable https://github.com/elastic/kibana/blob/da28df5b154bd8223124b1814f5b350b842c309d/src/core/MIGRATION.md#L1344 Moved the header assets to the new directory & updated the `toAssets` helper --- x-pack/index.js | 10 +--------- x-pack/legacy/plugins/ingest_manager/index.ts | 14 -------------- .../sections/epm/hooks/use_links.tsx | 5 +---- .../assets/illustration_integrations_darkmode.svg | 0 .../assets/illustration_integrations_lightmode.svg | 0 5 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 x-pack/legacy/plugins/ingest_manager/index.ts rename x-pack/plugins/ingest_manager/public/{applications/ingest_manager/sections/epm => }/assets/illustration_integrations_darkmode.svg (100%) rename x-pack/plugins/ingest_manager/public/{applications/ingest_manager/sections/epm => }/assets/illustration_integrations_lightmode.svg (100%) diff --git a/x-pack/index.js b/x-pack/index.js index 2d2e42650cfa..66fe05e8f035 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -9,15 +9,7 @@ import { monitoring } from './legacy/plugins/monitoring'; import { security } from './legacy/plugins/security'; import { beats } from './legacy/plugins/beats_management'; import { spaces } from './legacy/plugins/spaces'; -import { ingestManager } from './legacy/plugins/ingest_manager'; module.exports = function (kibana) { - return [ - xpackMain(kibana), - monitoring(kibana), - spaces(kibana), - security(kibana), - ingestManager(kibana), - beats(kibana), - ]; + return [xpackMain(kibana), monitoring(kibana), spaces(kibana), security(kibana), beats(kibana)]; }; diff --git a/x-pack/legacy/plugins/ingest_manager/index.ts b/x-pack/legacy/plugins/ingest_manager/index.ts deleted file mode 100644 index 2b20bf16f240..000000000000 --- a/x-pack/legacy/plugins/ingest_manager/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 { resolve } from 'path'; - -export function ingestManager(kibana: any) { - return new kibana.Plugin({ - id: 'ingestManager', - require: ['kibana', 'elasticsearch', 'xpack_main'], - publicDir: resolve(__dirname, '../../../plugins/ingest_manager/public'), - }); -} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx index 436163bafcfe..a453a7f2e28c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx @@ -13,10 +13,7 @@ const removeRelativePath = (relativePath: string): string => export function useLinks() { const { http } = useCore(); return { - toAssets: (path: string) => - http.basePath.prepend( - `/plugins/${PLUGIN_ID}/applications/ingest_manager/sections/epm/assets/${path}` - ), + toAssets: (path: string) => http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/${path}`), toImage: (path: string) => http.basePath.prepend(epmRouteService.getFilePath(path)), toRelativeImage: ({ path, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_darkmode.svg b/x-pack/plugins/ingest_manager/public/assets/illustration_integrations_darkmode.svg similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_darkmode.svg rename to x-pack/plugins/ingest_manager/public/assets/illustration_integrations_darkmode.svg diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_lightmode.svg b/x-pack/plugins/ingest_manager/public/assets/illustration_integrations_lightmode.svg similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_integrations_lightmode.svg rename to x-pack/plugins/ingest_manager/public/assets/illustration_integrations_lightmode.svg From eb84503d8abff3d80a0c8762b405dd815d350914 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 6 Jul 2020 12:56:26 -0700 Subject: [PATCH 20/21] upgrade caniuse-lite database (#70833) Co-authored-by: spalger --- yarn.lock | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index eb1943c5cd00..5efea82e84c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9550,20 +9550,10 @@ can-use-dom@^0.1.0: resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a" integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo= -caniuse-lite@^1.0.30000984, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001022: - version "1.0.30001022" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz#9eeffe580c3a8f110b7b1742dcf06a395885e4c6" - integrity sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A== - -caniuse-lite@^1.0.30001035: - version "1.0.30001036" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz#930ea5272010d8bf190d859159d757c0b398caf0" - integrity sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w== - -caniuse-lite@^1.0.30001043: - version "1.0.30001079" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001079.tgz#ed3e5225cd9a6850984fdd88bf24ce45d69b9c22" - integrity sha512-2KaYheg0iOY+CMmDuAB3DHehrXhhb4OZU4KBVGDr/YKyYAcpudaiUQ9PJ9rxrPlKEoJ3ATasQ5AN48MqpwS43Q== +caniuse-lite@^1.0.30000984, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001022, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001043: + version "1.0.30001094" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001094.tgz#0b11d02e1cdc201348dbd8e3e57bd9b6ce82b175" + integrity sha512-ufHZNtMaDEuRBpTbqD93tIQnngmJ+oBknjvr0IbFympSdtFpAUFmNv4mVKbb53qltxFx0nK3iy32S9AqkLzUNA== canvas@^2.6.1: version "2.6.1" From 11cfe80020d2fba1ab02ef8517e896744c85e35e Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Mon, 6 Jul 2020 15:33:27 -0500 Subject: [PATCH 21/21] [Metrics UI] Fix a bug in Metric Threshold query filter construction (#70672) Co-authored-by: Elastic Machine --- .../metric_threshold/lib/metric_query.test.ts | 59 +++++++++++++++++++ .../metric_threshold/lib/metric_query.ts | 13 ++-- 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts new file mode 100644 index 000000000000..3ad1031f574e --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { MetricExpressionParams } from '../types'; +import { getElasticsearchMetricQuery } from './metric_query'; + +describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { + const expressionParams = { + metric: 'system.is.a.good.puppy.dog', + aggType: 'avg', + timeUnit: 'm', + timeSize: 1, + } as MetricExpressionParams; + + const timefield = '@timestamp'; + const groupBy = 'host.doggoname'; + + describe('when passed no filterQuery', () => { + const searchBody = getElasticsearchMetricQuery(expressionParams, timefield, groupBy); + test('includes a range filter', () => { + expect( + searchBody.query.bool.filter.find((filter) => filter.hasOwnProperty('range')) + ).toBeTruthy(); + }); + + test('includes a metric field filter', () => { + expect(searchBody.query.bool.filter).toMatchObject( + expect.arrayContaining([{ exists: { field: 'system.is.a.good.puppy.dog' } }]) + ); + }); + }); + + describe('when passed a filterQuery', () => { + const filterQuery = + // This is adapted from a real-world query that previously broke alerts + // We want to make sure it doesn't override any existing filters + '{"bool":{"filter":[{"bool":{"filter":[{"bool":{"must_not":[{"bool":{"should":[{"query_string":{"query":"bark*","fields":["host.name^1.0"],"type":"best_fields","default_operator":"or","max_determinized_states":10000,"enable_position_increments":true,"fuzziness":"AUTO","fuzzy_prefix_length":0,"fuzzy_max_expansions":50,"phrase_slop":0,"escape":false,"auto_generate_synonyms_phrase_query":true,"fuzzy_transpositions":true,"boost":1}}],"adjust_pure_negative":true,"minimum_should_match":"1","boost":1}}],"adjust_pure_negative":true,"boost":1}},{"bool":{"must_not":[{"bool":{"should":[{"query_string":{"query":"woof*","fields":["host.name^1.0"],"type":"best_fields","default_operator":"or","max_determinized_states":10000,"enable_position_increments":true,"fuzziness":"AUTO","fuzzy_prefix_length":0,"fuzzy_max_expansions":50,"phrase_slop":0,"escape":false,"auto_generate_synonyms_phrase_query":true,"fuzzy_transpositions":true,"boost":1}}],"adjust_pure_negative":true,"minimum_should_match":"1","boost":1}}],"adjust_pure_negative":true,"boost":1}}],"adjust_pure_negative":true,"boost":1}}],"adjust_pure_negative":true,"boost":1}}'; + + const searchBody = getElasticsearchMetricQuery( + expressionParams, + timefield, + groupBy, + filterQuery + ); + test('includes a range filter', () => { + expect( + searchBody.query.bool.filter.find((filter) => filter.hasOwnProperty('range')) + ).toBeTruthy(); + }); + + test('includes a metric field filter', () => { + expect(searchBody.query.bool.filter).toMatchObject( + expect.arrayContaining([{ exists: { field: 'system.is.a.good.puppy.dog' } }]) + ); + }); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts index 5680035d9d60..15506a30529c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts @@ -11,11 +11,11 @@ import { createPercentileAggregation } from './create_percentile_aggregation'; const MINIMUM_BUCKETS = 5; -const getParsedFilterQuery: ( - filterQuery: string | undefined -) => Record | Array> = (filterQuery) => { - if (!filterQuery) return {}; - return JSON.parse(filterQuery).bool; +const getParsedFilterQuery: (filterQuery: string | undefined) => Record | null = ( + filterQuery +) => { + if (!filterQuery) return null; + return JSON.parse(filterQuery); }; export const getElasticsearchMetricQuery = ( @@ -129,9 +129,8 @@ export const getElasticsearchMetricQuery = ( filter: [ ...rangeFilters, ...metricFieldFilters, - ...(Array.isArray(parsedFilterQuery) ? parsedFilterQuery : []), + ...(parsedFilterQuery ? [parsedFilterQuery] : []), ], - ...(!Array.isArray(parsedFilterQuery) ? parsedFilterQuery : {}), }, }, size: 0,