From 4eb971c8c39f0ca5f3a443e996cdfa27676c1294 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 24 Apr 2020 13:38:27 +0200 Subject: [PATCH 01/13] [ML] EuiDataGrid ml/transform components. (#63447) Cleanup and consolidation of code related to EuiDataGrid. The transform wizard source and pivot preview table as well as the data frame analytics results pages now share a common code base related to data grid tables. To avoid tight coupling of components and hooks, the hooks are not within the common data grid component. Instead the hooks need to be used on the outer wrapping component and the results will be passed as props to the data grid component. This allows us to pass data from different data sources (transform index source, pivot previews, analytics results) into a shared component. --- .../components/data_grid}/common.test.ts | 60 +-- .../components/data_grid/common.ts | 287 ++++++++++++ .../components/data_grid/data_grid.tsx | 181 ++++++++ .../application/components/data_grid/index.ts | 23 + .../application/components/data_grid/types.ts | 98 ++++ .../components/data_grid/use_data_grid.ts | 112 +++++ .../data_frame_analytics/common/constants.ts | 10 + .../data_frame_analytics/common/data_grid.ts | 23 - .../data_frame_analytics/common/fields.ts | 426 +++++------------- .../common/get_index_data.ts | 68 +++ .../common/get_index_fields.ts | 49 ++ .../data_frame_analytics/common/index.ts | 15 +- .../common/use_results_view_config.ts | 104 +++++ .../classification_exploration.tsx | 181 +------- .../classification_exploration_data_grid.tsx | 135 ------ .../use_explore_data.ts | 292 ------------ .../error_callout/error_callout.tsx | 37 +- .../exploration_data_grid.tsx | 161 ------- .../exploration_page_wrapper.tsx | 76 ++++ .../exploration_page_wrapper}/index.ts | 2 +- .../exploration_results_table.tsx} | 117 ++--- .../exploration_results_table}/index.ts | 2 +- .../use_exploration_results.ts | 72 +++ .../exploration_title/exploration_title.tsx | 15 + .../components/exploration_title}/index.ts | 2 +- .../index.ts | 2 +- .../job_config_error_callout.tsx | 47 ++ .../outlier_exploration}/common.test.ts | 0 .../outlier_exploration}/common.ts | 15 +- .../outlier_exploration.tsx | 151 ++----- .../use_outlier_data.test.ts | 33 ++ .../outlier_exploration/use_outlier_data.ts | 117 +++++ .../regression_exploration/evaluate_panel.tsx | 2 +- .../regression_exploration.tsx | 171 +------ .../regression_exploration_data_grid.tsx | 135 ------ .../regression_exploration/results_table.tsx | 233 ---------- .../use_explore_data.ts | 291 ------------ .../use_explore_data/use_explore_data.ts | 267 ----------- .../analytics_list/action_clone.tsx | 3 +- .../public/__mocks__/shared_imports.ts | 20 +- .../public/app/common/data_grid.test.ts | 93 ++++ .../transform/public/app/common/data_grid.ts | 25 +- .../transform/public/app/common/index.ts | 6 +- .../app/components/pivot_preview/common.ts | 60 --- .../pivot_preview/pivot_preview.test.tsx | 55 --- .../pivot_preview/pivot_preview.tsx | 345 -------------- .../use_pivot_preview_data.test.tsx | 69 --- .../pivot_preview/use_pivot_preview_data.ts | 91 ---- .../public/app/hooks/use_index_data.test.tsx | 85 ++++ .../public/app/hooks/use_index_data.ts | 111 +++++ .../public/app/hooks/use_pivot_data.ts | 240 ++++++++++ .../__snapshots__/expanded_row.test.tsx.snap | 71 --- .../source_index_preview/common.test.ts | 35 -- .../components/source_index_preview/common.ts | 17 - .../expanded_row.test.tsx | 46 -- .../source_index_preview/expanded_row.tsx | 22 - .../source_index_preview.test.tsx | 38 -- .../source_index_preview.tsx | 293 ------------ .../use_source_index_data.test.tsx | 43 -- .../use_source_index_data.ts | 143 ------ .../step_define/step_define_form.tsx | 62 ++- .../step_define/step_define_summary.tsx | 51 ++- .../expanded_row_preview_pane.tsx | 26 +- .../transform/public/shared_imports.ts | 15 + .../translations/translations/ja-JP.json | 28 -- .../translations/translations/zh-CN.json | 28 -- .../apps/transform/creation_index_pattern.ts | 16 +- .../apps/transform/creation_saved_search.ts | 14 +- .../machine_learning/data_frame_analytics.ts | 4 +- .../services/transform_ui/wizard.ts | 16 +- 70 files changed, 2234 insertions(+), 3949 deletions(-) rename x-pack/plugins/{transform/public/app/components/pivot_preview => ml/public/application/components/data_grid}/common.test.ts (52%) create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/common.ts create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/index.ts create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/types.ts create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/common/constants.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/common/data_grid.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration_data_grid.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/use_explore_data.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/{hooks/use_explore_data => components/exploration_page_wrapper}/index.ts (73%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/{classification_exploration/results_table.tsx => exploration_results_table/exploration_results_table.tsx} (57%) rename x-pack/plugins/{transform/public/app/sections/create_transform/components/source_index_preview => ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table}/index.ts (77%) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx rename x-pack/plugins/{transform/public/app/components/pivot_preview => ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title}/index.ts (81%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/{exploration_data_grid => job_config_error_callout}/index.ts (78%) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/{hooks/use_explore_data => components/outlier_exploration}/common.test.ts (100%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/{hooks/use_explore_data => components/outlier_exploration}/common.ts (51%) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.test.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts create mode 100644 x-pack/plugins/transform/public/app/common/data_grid.test.ts delete mode 100644 x-pack/plugins/transform/public/app/components/pivot_preview/common.ts delete mode 100644 x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx delete mode 100644 x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx delete mode 100644 x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.test.tsx delete mode 100644 x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.ts create mode 100644 x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx create mode 100644 x-pack/plugins/transform/public/app/hooks/use_index_data.ts create mode 100644 x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/expanded_row.test.tsx.snap delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.test.ts delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.ts delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.tsx delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx delete mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/common.test.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts similarity index 52% rename from x-pack/plugins/transform/public/app/components/pivot_preview/common.test.ts rename to x-pack/plugins/ml/public/application/components/data_grid/common.test.ts index 172256ddb5cee..4bb670ad02dfc 100644 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/common.test.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts @@ -6,16 +6,7 @@ import { EuiDataGridSorting } from '@elastic/eui'; -import { - getPreviewRequestBody, - PivotAggsConfig, - PivotGroupByConfig, - PIVOT_SUPPORTED_AGGS, - PIVOT_SUPPORTED_GROUP_BY_AGGS, - SimpleQuery, -} from '../../common'; - -import { multiColumnSortFactory, getPivotPreviewDevConsoleStatement } from './common'; +import { multiColumnSortFactory } from './common'; describe('Transform: Define Pivot Common', () => { test('multiColumnSortFactory()', () => { @@ -65,53 +56,4 @@ describe('Transform: Define Pivot Common', () => { { s: 'a', n: 1 }, ]); }); - - test('getPivotPreviewDevConsoleStatement()', () => { - const query: SimpleQuery = { - query_string: { - query: '*', - default_operator: 'AND', - }, - }; - const groupBy: PivotGroupByConfig = { - agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, - field: 'the-group-by-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', - }; - const agg: PivotAggsConfig = { - agg: PIVOT_SUPPORTED_AGGS.AVG, - field: 'the-agg-field', - aggName: 'the-agg-agg-name', - dropDownName: 'the-agg-drop-down-name', - }; - const request = getPreviewRequestBody('the-index-pattern-title', query, [groupBy], [agg]); - const pivotPreviewDevConsoleStatement = getPivotPreviewDevConsoleStatement(request); - - expect(pivotPreviewDevConsoleStatement).toBe(`POST _transform/_preview -{ - "source": { - "index": [ - "the-index-pattern-title" - ] - }, - "pivot": { - "group_by": { - "the-group-by-agg-name": { - "terms": { - "field": "the-group-by-field" - } - } - }, - "aggregations": { - "the-agg-agg-name": { - "avg": { - "field": "the-agg-field" - } - } - } - } -} -`); - }); }); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts new file mode 100644 index 0000000000000..d141b68b5d03f --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -0,0 +1,287 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; +import { useEffect, useMemo } from 'react'; + +import { + EuiDataGridCellValueElementProps, + EuiDataGridSorting, + EuiDataGridStyle, +} from '@elastic/eui'; + +import { + IndexPattern, + IFieldType, + ES_FIELD_TYPES, + KBN_FIELD_TYPES, +} from '../../../../../../../src/plugins/data/public'; + +import { + BASIC_NUMERICAL_TYPES, + EXTENDED_NUMERICAL_TYPES, +} from '../../data_frame_analytics/common/fields'; + +import { + FEATURE_IMPORTANCE, + FEATURE_INFLUENCE, + OUTLIER_SCORE, +} from '../../data_frame_analytics/common/constants'; +import { formatHumanReadableDateTimeSeconds } from '../../util/date_utils'; +import { getNestedProperty } from '../../util/object_utils'; +import { mlFieldFormatService } from '../../services/field_format_service'; + +import { DataGridItem, IndexPagination, RenderCellValue } from './types'; + +export const INIT_MAX_COLUMNS = 20; + +export const euiDataGridStyle: EuiDataGridStyle = { + border: 'all', + fontSize: 's', + cellPadding: 's', + stripes: false, + rowHover: 'none', + header: 'shade', +}; + +export const euiDataGridToolbarSettings = { + showColumnSelector: true, + showStyleSelector: false, + showSortSelector: true, + showFullScreenSelector: false, +}; + +export const getFieldsFromKibanaIndexPattern = (indexPattern: IndexPattern): string[] => { + const allFields = indexPattern.fields.map(f => f.name); + const indexPatternFields: string[] = allFields.filter(f => { + if (indexPattern.metaFields.includes(f)) { + return false; + } + + const fieldParts = f.split('.'); + const lastPart = fieldParts.pop(); + if (lastPart === 'keyword' && allFields.includes(fieldParts.join('.'))) { + return false; + } + + return true; + }); + + return indexPatternFields; +}; + +export interface FieldTypes { + [key: string]: ES_FIELD_TYPES; +} + +export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, resultsField: string) => { + return Object.keys(fieldTypes).map(field => { + // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] + // To fall back to the default string schema it needs to be undefined. + let schema; + const isSortable = true; + const type = fieldTypes[field]; + + const isNumber = + type !== undefined && (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type)); + if (isNumber) { + schema = 'numeric'; + } + + switch (type) { + case 'date': + schema = 'datetime'; + break; + case 'geo_point': + schema = 'json'; + break; + case 'boolean': + schema = 'boolean'; + break; + } + + if ( + field === `${resultsField}.${OUTLIER_SCORE}` || + field.includes(`${resultsField}.${FEATURE_INFLUENCE}`) + ) { + schema = 'numeric'; + } + + if (field.includes(`${resultsField}.${FEATURE_IMPORTANCE}`)) { + schema = 'json'; + } + + return { id: field, schema, isSortable }; + }); +}; + +export const getDataGridSchemaFromKibanaFieldType = (field: IFieldType | undefined) => { + // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] + // To fall back to the default string schema it needs to be undefined. + let schema; + + switch (field?.type) { + case KBN_FIELD_TYPES.BOOLEAN: + schema = 'boolean'; + break; + case KBN_FIELD_TYPES.DATE: + schema = 'datetime'; + break; + case KBN_FIELD_TYPES.GEO_POINT: + case KBN_FIELD_TYPES.GEO_SHAPE: + schema = 'json'; + break; + case KBN_FIELD_TYPES.NUMBER: + schema = 'numeric'; + break; + } + + return schema; +}; + +export const useRenderCellValue = ( + indexPattern: IndexPattern | undefined, + pagination: IndexPagination, + tableItems: DataGridItem[], + resultsField?: string, + cellPropsCallback?: ( + columnId: string, + cellValue: any, + fullItem: Record, + setCellProps: EuiDataGridCellValueElementProps['setCellProps'] + ) => void +): RenderCellValue => { + const renderCellValue: RenderCellValue = useMemo(() => { + return ({ + rowIndex, + columnId, + setCellProps, + }: { + rowIndex: number; + columnId: string; + setCellProps: EuiDataGridCellValueElementProps['setCellProps']; + }) => { + const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; + + const fullItem = tableItems[adjustedRowIndex]; + + if (fullItem === undefined) { + return null; + } + + if (indexPattern === undefined) { + return null; + } + + let format: any; + + if (indexPattern !== undefined) { + format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, ''); + } + + function getCellValue(cId: string) { + if (cId.includes(`.${FEATURE_INFLUENCE}.`) && resultsField !== undefined) { + const results = getNestedProperty(tableItems[adjustedRowIndex], resultsField, null); + return results[cId.replace(`${resultsField}.`, '')]; + } + + return tableItems.hasOwnProperty(adjustedRowIndex) + ? getNestedProperty(tableItems[adjustedRowIndex], cId, null) + : null; + } + + const cellValue = getCellValue(columnId); + + // React by default doesn't all us to use a hook in a callback. + // However, this one will be passed on to EuiDataGrid and its docs + // recommend wrapping `setCellProps` in a `useEffect()` hook + // so we're ignoring the linting rule here. + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if (typeof cellPropsCallback === 'function') { + cellPropsCallback(columnId, cellValue, fullItem, setCellProps); + } + }, [columnId, cellValue]); + + if (typeof cellValue === 'object' && cellValue !== null) { + return JSON.stringify(cellValue); + } + + if (cellValue === undefined || cellValue === null) { + return null; + } + + if (format !== undefined) { + return format.convert(cellValue, 'text'); + } + + if (typeof cellValue === 'string' || cellValue === null) { + return cellValue; + } + + const field = indexPattern.fields.getByName(columnId); + if (field?.type === KBN_FIELD_TYPES.DATE) { + return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); + } + + if (typeof cellValue === 'boolean') { + return cellValue ? 'true' : 'false'; + } + + if (typeof cellValue === 'object' && cellValue !== null) { + return JSON.stringify(cellValue); + } + + return cellValue; + }; + }, [indexPattern?.fields, pagination.pageIndex, pagination.pageSize, tableItems]); + return renderCellValue; +}; + +/** + * Helper to sort an array of objects based on an EuiDataGrid sorting configuration. + * `sortFn()` is recursive to support sorting on multiple columns. + * + * @param sortingColumns - The EUI data grid sorting configuration + * @returns The sorting function which can be used with an array's sort() function. + */ +export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['columns']) => { + const isString = (arg: any): arg is string => { + return typeof arg === 'string'; + }; + + const sortFn = (a: any, b: any, sortingColumnIndex = 0): number => { + const sort = sortingColumns[sortingColumnIndex]; + const aValue = getNestedProperty(a, sort.id, null); + const bValue = getNestedProperty(b, sort.id, null); + + if (typeof aValue === 'number' && typeof bValue === 'number') { + if (aValue < bValue) { + return sort.direction === 'asc' ? -1 : 1; + } + if (aValue > bValue) { + return sort.direction === 'asc' ? 1 : -1; + } + } + + if (isString(aValue) && isString(bValue)) { + if (aValue.localeCompare(bValue) === -1) { + return sort.direction === 'asc' ? -1 : 1; + } + if (aValue.localeCompare(bValue) === 1) { + return sort.direction === 'asc' ? 1 : -1; + } + } + + if (sortingColumnIndex + 1 < sortingColumns.length) { + return sortFn(a, b, sortingColumnIndex + 1); + } + + return 0; + }; + + return sortFn; +}; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx new file mode 100644 index 0000000000000..a5b301902cc75 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, FC } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { + EuiButtonIcon, + EuiCallOut, + EuiCodeBlock, + EuiCopy, + EuiDataGrid, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; + +import { CoreSetup } from 'src/core/public'; + +import { INDEX_STATUS } from '../../data_frame_analytics/common'; + +import { euiDataGridStyle, euiDataGridToolbarSettings } from './common'; +import { UseIndexDataReturnType } from './types'; + +export const DataGridTitle: FC<{ title: string }> = ({ title }) => ( + + {title} + +); + +interface PropsWithoutHeader extends UseIndexDataReturnType { + dataTestSubj: string; + toastNotifications: CoreSetup['notifications']['toasts']; +} + +interface PropsWithHeader extends PropsWithoutHeader { + copyToClipboard: string; + copyToClipboardDescription: string; + title: string; +} + +function isWithHeader(arg: any): arg is PropsWithHeader { + return typeof arg?.title === 'string' && arg?.title !== ''; +} + +type Props = PropsWithHeader | PropsWithoutHeader; + +export const DataGrid: FC = props => { + const { + columns, + dataTestSubj, + errorMessage, + invalidSortingColumnns, + noDataMessage, + onChangeItemsPerPage, + onChangePage, + onSort, + pagination, + setVisibleColumns, + renderCellValue, + rowCount, + sortingColumns, + status, + tableItems: data, + toastNotifications, + visibleColumns, + } = props; + + useEffect(() => { + if (invalidSortingColumnns.length > 0) { + invalidSortingColumnns.forEach(columnId => { + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataGrid.invalidSortingColumnError', { + defaultMessage: `The column '{columnId}' cannot be used for sorting.`, + values: { columnId }, + }) + ); + }); + } + }, [invalidSortingColumnns, toastNotifications]); + + if (status === INDEX_STATUS.LOADED && data.length === 0) { + return ( +
+ {isWithHeader(props) && } + +

+ {i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutBody', { + defaultMessage: + 'The query for the index returned no results. Please make sure you have sufficient permissions, the index contains documents and your query is not too restrictive.', + })} +

+
+
+ ); + } + + if (noDataMessage !== '') { + return ( +
+ {isWithHeader(props) && } + +

{noDataMessage}

+
+
+ ); + } + + return ( +
+ {isWithHeader(props) && ( + + + + + + + {(copy: () => void) => ( + + )} + + + + )} + {status === INDEX_STATUS.ERROR && ( +
+ + + {errorMessage} + + + +
+ )} + +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts new file mode 100644 index 0000000000000..2472878d1b0c1 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { + getDataGridSchemasFromFieldTypes, + getDataGridSchemaFromKibanaFieldType, + getFieldsFromKibanaIndexPattern, + multiColumnSortFactory, + useRenderCellValue, +} from './common'; +export { useDataGrid } from './use_data_grid'; +export { DataGrid } from './data_grid'; +export { + DataGridItem, + EsSorting, + RenderCellValue, + SearchResponse7, + UseDataGridReturnType, + UseIndexDataReturnType, +} from './types'; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/types.ts b/x-pack/plugins/ml/public/application/components/data_grid/types.ts new file mode 100644 index 0000000000000..5fa038edf7815 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/types.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Dispatch, SetStateAction } from 'react'; +import { SearchResponse } from 'elasticsearch'; + +import { EuiDataGridPaginationProps, EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui'; + +import { Dictionary } from '../../../../common/types/common'; + +import { INDEX_STATUS } from '../../data_frame_analytics/common/analytics'; + +export type ColumnId = string; +export type DataGridItem = Record; + +export type IndexPagination = Pick; + +export type OnChangeItemsPerPage = (pageSize: any) => void; +export type OnChangePage = (pageIndex: any) => void; +export type OnSort = ( + sc: Array<{ + id: string; + direction: 'asc' | 'desc'; + }> +) => void; + +export type RenderCellValue = ({ + rowIndex, + columnId, + setCellProps, +}: { + rowIndex: number; + columnId: string; + setCellProps: any; +}) => any; + +export type EsSorting = Dictionary<{ + order: 'asc' | 'desc'; +}>; + +// The types specified in `@types/elasticsearch` are out of date and still have `total: number`. +export interface SearchResponse7 extends SearchResponse { + hits: SearchResponse['hits'] & { + total: { + value: number; + relation: string; + }; + }; +} + +export interface UseIndexDataReturnType + extends Pick< + UseDataGridReturnType, + | 'errorMessage' + | 'invalidSortingColumnns' + | 'noDataMessage' + | 'onChangeItemsPerPage' + | 'onChangePage' + | 'onSort' + | 'pagination' + | 'setPagination' + | 'setVisibleColumns' + | 'rowCount' + | 'sortingColumns' + | 'status' + | 'tableItems' + | 'visibleColumns' + > { + columns: EuiDataGridColumn[]; + renderCellValue: RenderCellValue; +} + +export interface UseDataGridReturnType { + errorMessage: string; + invalidSortingColumnns: ColumnId[]; + noDataMessage: string; + onChangeItemsPerPage: OnChangeItemsPerPage; + onChangePage: OnChangePage; + onSort: OnSort; + pagination: IndexPagination; + resetPagination: () => void; + rowCount: number; + setErrorMessage: Dispatch>; + setNoDataMessage: Dispatch>; + setPagination: Dispatch>; + setRowCount: Dispatch>; + setSortingColumns: Dispatch>; + setStatus: Dispatch>; + setTableItems: Dispatch>; + setVisibleColumns: Dispatch>; + sortingColumns: EuiDataGridSorting['columns']; + status: INDEX_STATUS; + tableItems: DataGridItem[]; + visibleColumns: ColumnId[]; +} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts new file mode 100644 index 0000000000000..c7c4f46031b6e --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useEffect, useState } from 'react'; + +import { EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui'; + +import { INDEX_STATUS } from '../../data_frame_analytics/common'; + +import { INIT_MAX_COLUMNS } from './common'; +import { + ColumnId, + DataGridItem, + IndexPagination, + OnChangeItemsPerPage, + OnChangePage, + OnSort, + UseDataGridReturnType, +} from './types'; + +export const useDataGrid = ( + columns: EuiDataGridColumn[], + defaultPageSize = 5, + defaultVisibleColumnsCount = INIT_MAX_COLUMNS, + defaultVisibleColumnsFilter?: (id: string) => boolean +): UseDataGridReturnType => { + const defaultPagination: IndexPagination = { pageIndex: 0, pageSize: defaultPageSize }; + + const [noDataMessage, setNoDataMessage] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [status, setStatus] = useState(INDEX_STATUS.UNUSED); + const [rowCount, setRowCount] = useState(0); + const [tableItems, setTableItems] = useState([]); + const [pagination, setPagination] = useState(defaultPagination); + const [sortingColumns, setSortingColumns] = useState([]); + + const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback(pageSize => { + setPagination(p => { + const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); + return { pageIndex, pageSize }; + }); + }, []); + + const onChangePage: OnChangePage = useCallback( + pageIndex => setPagination(p => ({ ...p, pageIndex })), + [] + ); + + const resetPagination = () => setPagination(defaultPagination); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState([]); + + const columnIds = columns.map(c => c.id); + const filteredColumnIds = + defaultVisibleColumnsFilter !== undefined + ? columnIds.filter(defaultVisibleColumnsFilter) + : columnIds; + const defaultVisibleColumns = filteredColumnIds.splice(0, defaultVisibleColumnsCount); + + useEffect(() => { + setVisibleColumns(defaultVisibleColumns); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultVisibleColumns.join()]); + + const [invalidSortingColumnns, setInvalidSortingColumnns] = useState([]); + + const onSort: OnSort = useCallback( + sc => { + // Check if an unsupported column type for sorting was selected. + const updatedInvalidSortingColumnns = sc.reduce((arr, current) => { + const columnType = columns.find(dgc => dgc.id === current.id); + if (columnType?.schema === 'json') { + arr.push(current.id); + } + return arr; + }, []); + setInvalidSortingColumnns(updatedInvalidSortingColumnns); + if (updatedInvalidSortingColumnns.length === 0) { + setSortingColumns(sc); + } + }, + [columns] + ); + + return { + errorMessage, + invalidSortingColumnns, + noDataMessage, + onChangeItemsPerPage, + onChangePage, + onSort, + pagination, + resetPagination, + rowCount, + setErrorMessage, + setNoDataMessage, + setPagination, + setRowCount, + setSortingColumns, + setStatus, + setTableItems, + setVisibleColumns, + sortingColumns, + status, + tableItems, + visibleColumns, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/constants.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/constants.ts new file mode 100644 index 0000000000000..51b2918012c8d --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const DEFAULT_RESULTS_FIELD = 'ml'; +export const FEATURE_IMPORTANCE = 'feature_importance'; +export const FEATURE_INFLUENCE = 'feature_influence'; +export const OUTLIER_SCORE = 'outlier_score'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/data_grid.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/data_grid.ts deleted file mode 100644 index 2b6d733837562..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/data_grid.ts +++ /dev/null @@ -1,23 +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 { EuiDataGridStyle } from '@elastic/eui'; - -export const euiDataGridStyle: EuiDataGridStyle = { - border: 'all', - fontSize: 's', - cellPadding: 's', - stripes: false, - rowHover: 'none', - header: 'shade', -}; - -export const euiDataGridToolbarSettings = { - showColumnSelector: true, - showStyleSelector: false, - showSortSelector: true, - showFullScreenSelector: false, -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index f165669bdd674..8423bc1b94a09 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -4,18 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getNestedProperty } from '../../util/object_utils'; import { - DataFrameAnalyticsConfig, getNumTopFeatureImportanceValues, getPredictedFieldName, getDependentVar, getPredictionFieldName, + isClassificationAnalysis, + isOutlierAnalysis, + isRegressionAnalysis, + DataFrameAnalyticsConfig, } from './analytics'; import { Field } from '../../../../common/types/fields'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; import { newJobCapsService } from '../../services/new_job_capabilities_service'; +import { FEATURE_IMPORTANCE, FEATURE_INFLUENCE, OUTLIER_SCORE } from './constants'; + export type EsId = string; export type EsDocSource = Record; export type EsFieldName = string; @@ -42,7 +46,7 @@ export const EXTENDED_NUMERICAL_TYPES = new Set([ ES_FIELD_TYPES.SCALED_FLOAT, ]); -const ML__ID_COPY = 'ml__id_copy'; +export const ML__ID_COPY = 'ml__id_copy'; export const isKeywordAndTextType = (fieldName: string): boolean => { const { fields } = newJobCapsService; @@ -64,32 +68,61 @@ export const isKeywordAndTextType = (fieldName: string): boolean => { }; // Used to sort columns: +// - Anchor on the left ml.outlier_score, ml.is_training, , // - string based columns are moved to the left -// - followed by the outlier_score column -// - feature_influence fields get moved next to the corresponding field column +// - feature_influence/feature_importance fields get moved next to the corresponding field column // - overall fields get sorted alphabetically -export const sortColumns = (obj: EsDocSource, resultsField: string) => (a: string, b: string) => { - const typeofA = typeof obj[a]; - const typeofB = typeof obj[b]; +export const sortExplorationResultsFields = ( + a: string, + b: string, + jobConfig: DataFrameAnalyticsConfig +) => { + const resultsField = jobConfig.dest.results_field; - if (typeofA !== 'string' && typeofB === 'string') { - return 1; - } - if (typeofA === 'string' && typeofB !== 'string') { - return -1; - } - if (typeofA === 'string' && typeofB === 'string') { - return a.localeCompare(b); - } + if (isOutlierAnalysis(jobConfig.analysis)) { + if (a === `${resultsField}.${OUTLIER_SCORE}`) { + return -1; + } - if (a === `${resultsField}.outlier_score`) { - return -1; + if (b === `${resultsField}.${OUTLIER_SCORE}`) { + return 1; + } } - if (b === `${resultsField}.outlier_score`) { - return 1; + if (isClassificationAnalysis(jobConfig.analysis) || isRegressionAnalysis(jobConfig.analysis)) { + const dependentVariable = getDependentVar(jobConfig.analysis); + const predictedField = getPredictedFieldName(resultsField, jobConfig.analysis, true); + + if (a === `${resultsField}.is_training`) { + return -1; + } + if (b === `${resultsField}.is_training`) { + return 1; + } + if (a === predictedField) { + return -1; + } + if (b === predictedField) { + return 1; + } + if (a === dependentVariable || a === dependentVariable.replace(/\.keyword$/, '')) { + return -1; + } + if (b === dependentVariable || b === dependentVariable.replace(/\.keyword$/, '')) { + return 1; + } + + if (a === `${resultsField}.prediction_probability`) { + return -1; + } + if (b === `${resultsField}.prediction_probability`) { + return 1; + } } + const typeofA = typeof a; + const typeofB = typeof b; + const tokensA = a.split('.'); const prefixA = tokensA[0]; const tokensB = b.split('.'); @@ -109,91 +142,6 @@ export const sortColumns = (obj: EsDocSource, resultsField: string) => (a: strin return a.localeCompare(tokensB.join('.')); } - return a.localeCompare(b); -}; - -export const sortRegressionResultsFields = ( - a: string, - b: string, - jobConfig: DataFrameAnalyticsConfig -) => { - const dependentVariable = getDependentVar(jobConfig.analysis); - const resultsField = jobConfig.dest.results_field; - const predictedField = getPredictedFieldName(resultsField, jobConfig.analysis, true); - if (a === `${resultsField}.is_training`) { - return -1; - } - if (b === `${resultsField}.is_training`) { - return 1; - } - if (a === predictedField) { - return -1; - } - if (b === predictedField) { - return 1; - } - if (a === dependentVariable || a === dependentVariable.replace(/\.keyword$/, '')) { - return -1; - } - if (b === dependentVariable || b === dependentVariable.replace(/\.keyword$/, '')) { - return 1; - } - - if (a === `${resultsField}.prediction_probability`) { - return -1; - } - if (b === `${resultsField}.prediction_probability`) { - return 1; - } - - return a.localeCompare(b); -}; - -// Used to sort columns: -// Anchor on the left ml.is_training, , -export const sortRegressionResultsColumns = ( - obj: EsDocSource, - jobConfig: DataFrameAnalyticsConfig -) => (a: string, b: string) => { - const dependentVariable = getDependentVar(jobConfig.analysis); - const resultsField = jobConfig.dest.results_field; - const predictedField = getPredictedFieldName(resultsField, jobConfig.analysis, true); - - const typeofA = typeof obj[a]; - const typeofB = typeof obj[b]; - - if (a === `${resultsField}.is_training`) { - return -1; - } - - if (b === `${resultsField}.is_training`) { - return 1; - } - - if (a === predictedField) { - return -1; - } - - if (b === predictedField) { - return 1; - } - - if (a === dependentVariable) { - return -1; - } - - if (b === dependentVariable) { - return 1; - } - - if (a === `${resultsField}.prediction_probability`) { - return -1; - } - - if (b === `${resultsField}.prediction_probability`) { - return 1; - } - if (typeofA !== 'string' && typeofB === 'string') { return 1; } @@ -204,44 +152,9 @@ export const sortRegressionResultsColumns = ( return a.localeCompare(b); } - const tokensA = a.split('.'); - const prefixA = tokensA[0]; - const tokensB = b.split('.'); - const prefixB = tokensB[0]; - - if (prefixA === resultsField && tokensA.length > 1 && prefixB !== resultsField) { - tokensA.shift(); - tokensA.shift(); - if (tokensA.join('.') === b) return 1; - return tokensA.join('.').localeCompare(b); - } - - if (prefixB === resultsField && tokensB.length > 1 && prefixA !== resultsField) { - tokensB.shift(); - tokensB.shift(); - if (tokensB.join('.') === a) return -1; - return a.localeCompare(tokensB.join('.')); - } - return a.localeCompare(b); }; -export function getFlattenedFields(obj: EsDocSource, resultsField: string): EsFieldName[] { - const flatDocFields: EsFieldName[] = []; - const newDocFields = Object.keys(obj); - newDocFields.forEach(f => { - const fieldValue = getNestedProperty(obj, f); - if (typeof fieldValue !== 'object' || fieldValue === null || Array.isArray(fieldValue)) { - flatDocFields.push(f); - } else { - const innerFields = getFlattenedFields(fieldValue, resultsField); - const flattenedFields = innerFields.map(d => `${f}.${d}`); - flatDocFields.push(...flattenedFields); - } - }); - return flatDocFields.filter(f => f !== ML__ID_COPY); -} - export const getDefaultFieldsFromJobCaps = ( fields: Field[], jobConfig: DataFrameAnalyticsConfig, @@ -259,49 +172,72 @@ export const getDefaultFieldsFromJobCaps = ( return fieldsObj; } - const dependentVariable = getDependentVar(jobConfig.analysis); - const type = newJobCapsService.getFieldById(dependentVariable)?.type; - const predictionFieldName = getPredictionFieldName(jobConfig.analysis); - const numTopFeatureImportanceValues = getNumTopFeatureImportanceValues(jobConfig.analysis); // default is 'ml' const resultsField = jobConfig.dest.results_field; - const defaultPredictionField = `${dependentVariable}_prediction`; - const predictedField = `${resultsField}.${ - predictionFieldName ? predictionFieldName : defaultPredictionField - }`; - const featureImportanceFields = []; - - if ((numTopFeatureImportanceValues ?? 0) > 0) { - featureImportanceFields.push({ - id: `${resultsField}.feature_importance`, - name: `${resultsField}.feature_importance`, - type: KBN_FIELD_TYPES.NUMBER, - }); + const featureInfluenceFields = []; + const allFields: any = []; + let type: ES_FIELD_TYPES | undefined; + let predictedField: string | undefined; + + if (isOutlierAnalysis(jobConfig.analysis)) { + // Only need to add these fields if we didn't use dest index pattern to get the fields + if (needsDestIndexFields === true) { + allFields.push({ + id: `${resultsField}.${OUTLIER_SCORE}`, + name: `${resultsField}.${OUTLIER_SCORE}`, + type: KBN_FIELD_TYPES.NUMBER, + }); + + featureInfluenceFields.push( + ...fields + .filter(d => !jobConfig.analyzed_fields.excludes.includes(d.id)) + .map(d => ({ + id: `${resultsField}.${FEATURE_INFLUENCE}.${d.id}`, + name: `${resultsField}.${FEATURE_INFLUENCE}.${d.name}`, + type: KBN_FIELD_TYPES.NUMBER, + })) + ); + } } - let allFields: any = []; - // Only need to add these fields if we didn't use dest index pattern to get the fields - if (needsDestIndexFields === true) { - allFields.push( - { - id: `${resultsField}.is_training`, - name: `${resultsField}.is_training`, - type: ES_FIELD_TYPES.BOOLEAN, - }, - { id: predictedField, name: predictedField, type } - ); + if (isClassificationAnalysis(jobConfig.analysis) || isRegressionAnalysis(jobConfig.analysis)) { + const dependentVariable = getDependentVar(jobConfig.analysis); + type = newJobCapsService.getFieldById(dependentVariable)?.type; + const predictionFieldName = getPredictionFieldName(jobConfig.analysis); + const numTopFeatureImportanceValues = getNumTopFeatureImportanceValues(jobConfig.analysis); + + const defaultPredictionField = `${dependentVariable}_prediction`; + predictedField = `${resultsField}.${ + predictionFieldName ? predictionFieldName : defaultPredictionField + }`; + + if ((numTopFeatureImportanceValues ?? 0) > 0 && needsDestIndexFields === true) { + featureImportanceFields.push({ + id: `${resultsField}.${FEATURE_IMPORTANCE}`, + name: `${resultsField}.${FEATURE_IMPORTANCE}`, + type: KBN_FIELD_TYPES.UNKNOWN, + }); + } + + // Only need to add these fields if we didn't use dest index pattern to get the fields + if (needsDestIndexFields === true) { + allFields.push( + { + id: `${resultsField}.is_training`, + name: `${resultsField}.is_training`, + type: ES_FIELD_TYPES.BOOLEAN, + }, + { id: predictedField, name: predictedField, type } + ); + } } - allFields.push(...fields, ...featureImportanceFields); + allFields.push(...fields, ...featureImportanceFields, ...featureInfluenceFields); allFields.sort(({ name: a }: { name: string }, { name: b }: { name: string }) => - sortRegressionResultsFields(a, b, jobConfig) + sortExplorationResultsFields(a, b, jobConfig) ); - // Remove feature_importance fields provided by dest index since feature_importance is an array the path is not valid - if (needsDestIndexFields === false) { - allFields = allFields.filter((field: any) => !field.name.includes('.feature_importance.')); - } let selectedFields = allFields.filter( (field: any) => field.name === predictedField || !field.name.includes('.keyword') @@ -317,145 +253,3 @@ export const getDefaultFieldsFromJobCaps = ( depVarType: type, }; }; - -export const getDefaultClassificationFields = ( - docs: EsDoc[], - jobConfig: DataFrameAnalyticsConfig -): EsFieldName[] => { - if (docs.length === 0) { - return []; - } - const resultsField = jobConfig.dest.results_field; - const newDocFields = getFlattenedFields(docs[0]._source, resultsField); - return newDocFields - .filter(k => { - if (k === `${resultsField}.is_training`) { - return true; - } - // predicted value of dependent variable - if (k === getPredictedFieldName(resultsField, jobConfig.analysis, true)) { - return true; - } - // actual value of dependent variable - if (k === getDependentVar(jobConfig.analysis)) { - return true; - } - - if (k === `${resultsField}.prediction_probability`) { - return true; - } - - if (k.split('.')[0] === resultsField) { - return false; - } - - return docs.some(row => row._source[k] !== null); - }) - .sort((a, b) => sortRegressionResultsFields(a, b, jobConfig)) - .slice(0, DEFAULT_REGRESSION_COLUMNS); -}; - -export const getDefaultRegressionFields = ( - docs: EsDoc[], - jobConfig: DataFrameAnalyticsConfig -): EsFieldName[] => { - const resultsField = jobConfig.dest.results_field; - if (docs.length === 0) { - return []; - } - - const newDocFields = getFlattenedFields(docs[0]._source, resultsField); - return newDocFields - .filter(k => { - if (k === `${resultsField}.is_training`) { - return true; - } - // predicted value of dependent variable - if (k === getPredictedFieldName(resultsField, jobConfig.analysis)) { - return true; - } - // actual value of dependent variable - if (k === getDependentVar(jobConfig.analysis)) { - return true; - } - if (k.split('.')[0] === resultsField) { - return false; - } - - return docs.some(row => row._source[k] !== null); - }) - .sort((a, b) => sortRegressionResultsFields(a, b, jobConfig)) - .slice(0, DEFAULT_REGRESSION_COLUMNS); -}; - -export const getDefaultSelectableFields = (docs: EsDoc[], resultsField: string): EsFieldName[] => { - if (docs.length === 0) { - return []; - } - - const newDocFields = getFlattenedFields(docs[0]._source, resultsField); - return newDocFields.filter(k => { - if (k === `${resultsField}.outlier_score`) { - return true; - } - if (k.split('.')[0] === resultsField) { - return false; - } - - return docs.some(row => row._source[k] !== null); - }); -}; - -export const toggleSelectedFieldSimple = ( - selectedFields: EsFieldName[], - column: EsFieldName -): EsFieldName[] => { - const index = selectedFields.indexOf(column); - - if (index === -1) { - selectedFields.push(column); - } else { - selectedFields.splice(index, 1); - } - return selectedFields; -}; -// Fields starting with 'ml' or custom result name not included in newJobCapsService fields so -// need to recreate the field with correct type and add to selected fields -export const toggleSelectedField = ( - selectedFields: Field[], - column: EsFieldName, - resultsField: string, - depVarType?: ES_FIELD_TYPES -): Field[] => { - const index = selectedFields.map(field => field.name).indexOf(column); - if (index === -1) { - const columnField = newJobCapsService.getFieldById(column); - if (columnField !== null) { - selectedFields.push(columnField); - } else { - const resultFieldPattern = `^${resultsField}\.`; - const regex = new RegExp(resultFieldPattern); - const isResultField = column.match(regex) !== null; - let newField; - - if (isResultField && column.includes('is_training')) { - newField = { - id: column, - name: column, - type: ES_FIELD_TYPES.BOOLEAN, - }; - } else if (isResultField && depVarType !== undefined) { - newField = { - id: column, - name: column, - type: depVarType, - }; - } - - if (newField) selectedFields.push(newField); - } - } else { - selectedFields.splice(index, 1); - } - return selectedFields; -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts new file mode 100644 index 0000000000000..87b8c15aeaa78 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getErrorMessage } from '../../../../common/util/errors'; + +import { EsSorting, SearchResponse7, UseDataGridReturnType } from '../../components/data_grid'; +import { ml } from '../../services/ml_api_service'; + +import { isKeywordAndTextType } from '../common/fields'; +import { SavedSearchQuery } from '../../contexts/ml'; + +import { DataFrameAnalyticsConfig, INDEX_STATUS } from './analytics'; + +export const getIndexData = async ( + jobConfig: DataFrameAnalyticsConfig | undefined, + dataGrid: UseDataGridReturnType, + searchQuery: SavedSearchQuery +) => { + if (jobConfig !== undefined) { + const { + pagination, + setErrorMessage, + setRowCount, + setStatus, + setTableItems, + sortingColumns, + } = dataGrid; + + setErrorMessage(''); + setStatus(INDEX_STATUS.LOADING); + + try { + const sort: EsSorting = sortingColumns + .map(column => { + const { id } = column; + column.id = isKeywordAndTextType(id) ? `${id}.keyword` : id; + return column; + }) + .reduce((s, column) => { + s[column.id] = { order: column.direction }; + return s; + }, {} as EsSorting); + + const { pageIndex, pageSize } = pagination; + const resp: SearchResponse7 = await ml.esSearch({ + index: jobConfig.dest.index, + body: { + query: searchQuery, + from: pageIndex * pageSize, + size: pageSize, + ...(Object.keys(sort).length > 0 ? { sort } : {}), + }, + }); + + setRowCount(resp.hits.total.value); + + const docs = resp.hits.hits.map(d => d._source); + setTableItems(docs); + setStatus(INDEX_STATUS.LOADED); + } catch (e) { + setErrorMessage(getErrorMessage(e)); + setStatus(INDEX_STATUS.ERROR); + } + } +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts new file mode 100644 index 0000000000000..12ae4a586e949 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_fields.ts @@ -0,0 +1,49 @@ +/* + * 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 { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; + +import { newJobCapsService } from '../../services/new_job_capabilities_service'; + +import { getDefaultFieldsFromJobCaps, DataFrameAnalyticsConfig } from '../common'; + +export interface FieldTypes { + [key: string]: ES_FIELD_TYPES; +} + +export const getIndexFields = ( + jobConfig: DataFrameAnalyticsConfig | undefined, + needsDestIndexFields: boolean +) => { + const { fields } = newJobCapsService; + if (jobConfig !== undefined) { + const { selectedFields: defaultSelected, docFields } = getDefaultFieldsFromJobCaps( + fields, + jobConfig, + needsDestIndexFields + ); + + const types: FieldTypes = {}; + const allFields: string[] = []; + + docFields.forEach(field => { + types[field.id] = field.type; + allFields.push(field.id); + }); + + return { + defaultSelectedFields: defaultSelected.map(field => field.id), + fieldTypes: types, + tableFields: allFields, + }; + } else { + return { + defaultSelectedFields: [], + fieldTypes: {}, + tableFields: [], + }; + } +}; 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 7b76faf613ce8..400902c152c9e 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 @@ -30,16 +30,8 @@ export { } from './analytics'; export { - getDefaultSelectableFields, - getDefaultRegressionFields, - getDefaultClassificationFields, getDefaultFieldsFromJobCaps, - getFlattenedFields, - sortColumns, - sortRegressionResultsColumns, - sortRegressionResultsFields, - toggleSelectedField, - toggleSelectedFieldSimple, + sortExplorationResultsFields, EsId, EsDoc, EsDocSource, @@ -47,4 +39,7 @@ export { MAX_COLUMNS, } from './fields'; -export { euiDataGridStyle, euiDataGridToolbarSettings } from './data_grid'; +export { getIndexData } from './get_index_data'; +export { getIndexFields } from './get_index_fields'; + +export { useResultsViewConfig } from './use_results_view_config'; 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 new file mode 100644 index 0000000000000..0bc9e78207596 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState } from 'react'; + +import { IndexPattern } from '../../../../../../../src/plugins/data/public'; + +import { getErrorMessage } from '../../../../common/util/errors'; + +import { getIndexPatternIdFromName } from '../../util/index_utils'; +import { ml } from '../../services/ml_api_service'; +import { newJobCapsService } from '../../services/new_job_capabilities_service'; +import { useMlContext } from '../../contexts/ml'; + +import { DataFrameAnalyticsConfig } from '../common'; + +import { isGetDataFrameAnalyticsStatsResponseOk } from '../pages/analytics_management/services/analytics_service/get_analytics'; +import { DATA_FRAME_TASK_STATE } from '../pages/analytics_management/components/analytics_list/common'; + +export const useResultsViewConfig = (jobId: string) => { + const mlContext = useMlContext(); + const [indexPattern, setIndexPattern] = useState(undefined); + const [isInitialized, setIsInitialized] = useState(false); + const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false); + const [jobConfig, setJobConfig] = useState(undefined); + const [jobCapsServiceErrorMessage, setJobCapsServiceErrorMessage] = useState( + undefined + ); + const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState(undefined); + const [jobStatus, setJobStatus] = useState(undefined); + + // get analytics configuration, index pattern and field caps + useEffect(() => { + (async function() { + setIsLoadingJobConfig(false); + + try { + const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId); + const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId); + const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats) + ? analyticsStats.data_frame_analytics[0] + : undefined; + + if (stats !== undefined && stats.state) { + setJobStatus(stats.state); + } + + if ( + Array.isArray(analyticsConfigs.data_frame_analytics) && + analyticsConfigs.data_frame_analytics.length > 0 + ) { + const jobConfigUpdate = analyticsConfigs.data_frame_analytics[0]; + + try { + const destIndex = Array.isArray(jobConfigUpdate.dest.index) + ? jobConfigUpdate.dest.index[0] + : jobConfigUpdate.dest.index; + const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex; + let indexP: IndexPattern | undefined; + + try { + indexP = await mlContext.indexPatterns.get(destIndexPatternId); + } catch (e) { + indexP = undefined; + } + + if (indexP === undefined) { + const sourceIndex = jobConfigUpdate.source.index[0]; + const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; + indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); + } + + if (indexP !== undefined) { + await newJobCapsService.initializeFromIndexPattern(indexP, false, false); + setJobConfig(analyticsConfigs.data_frame_analytics[0]); + setIndexPattern(indexP); + setIsInitialized(true); + setIsLoadingJobConfig(false); + } + } catch (e) { + setJobCapsServiceErrorMessage(getErrorMessage(e)); + setIsLoadingJobConfig(false); + } + } + } catch (e) { + setJobConfigErrorMessage(getErrorMessage(e)); + setIsLoadingJobConfig(false); + } + })(); + }, []); + + return { + indexPattern, + isInitialized, + isLoadingJobConfig, + jobCapsServiceErrorMessage, + jobConfig, + jobConfigErrorMessage, + jobStatus, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx index 5c151166829ab..ccac9a697210b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx @@ -4,183 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useState, useEffect } from 'react'; -import { EuiCallOut, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { ml } from '../../../../../services/ml_api_service'; -import { DataFrameAnalyticsConfig } from '../../../../common'; -import { EvaluatePanel } from './evaluate_panel'; -import { ResultsTable } from './results_table'; -import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; -import { ResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics'; -import { LoadingPanel } from '../loading_panel'; -import { getIndexPatternIdFromName } from '../../../../../util/index_utils'; -import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; -import { useMlContext } from '../../../../../contexts/ml'; -import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics'; +import React, { FC } from 'react'; -export const ExplorationTitle: React.FC<{ jobId: string }> = ({ jobId }) => ( - - - {i18n.translate('xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle', { - defaultMessage: 'Destination index for classification job ID {jobId}', - values: { jobId }, - })} - - -); +import { i18n } from '@kbn/i18n'; -const jobConfigErrorTitle = i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.jobConfigurationFetchError', - { - defaultMessage: - 'Unable to fetch results. An error occurred loading the job configuration data.', - } -); +import { ExplorationPageWrapper } from '../exploration_page_wrapper'; -const jobCapsErrorTitle = i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.jobCapsFetchError', - { - defaultMessage: "Unable to fetch results. An error occurred loading the index's field data.", - } -); +import { EvaluatePanel } from './evaluate_panel'; interface Props { jobId: string; } export const ClassificationExploration: FC = ({ jobId }) => { - const [jobConfig, setJobConfig] = useState(undefined); - const [jobStatus, setJobStatus] = useState(undefined); - const [indexPattern, setIndexPattern] = useState(undefined); - const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false); - const [isInitialized, setIsInitialized] = useState(false); - const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState(undefined); - const [jobCapsServiceErrorMessage, setJobCapsServiceErrorMessage] = useState( - undefined - ); - const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); - const mlContext = useMlContext(); - - const loadJobConfig = async () => { - setIsLoadingJobConfig(true); - try { - const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId); - const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId); - const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats) - ? analyticsStats.data_frame_analytics[0] - : undefined; - - if (stats !== undefined && stats.state) { - setJobStatus(stats.state); - } - - if ( - Array.isArray(analyticsConfigs.data_frame_analytics) && - analyticsConfigs.data_frame_analytics.length > 0 - ) { - setJobConfig(analyticsConfigs.data_frame_analytics[0]); - setIsLoadingJobConfig(false); - } else { - setJobConfigErrorMessage( - i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.jobConfigurationNoResultsMessage', - { - defaultMessage: 'No results found.', - } - ) - ); - } - } catch (e) { - if (e.message !== undefined) { - setJobConfigErrorMessage(e.message); - } else { - setJobConfigErrorMessage(JSON.stringify(e)); - } - setIsLoadingJobConfig(false); - } - }; - - useEffect(() => { - loadJobConfig(); - }, []); - - const initializeJobCapsService = async () => { - if (jobConfig !== undefined) { - try { - const destIndex = Array.isArray(jobConfig.dest.index) - ? jobConfig.dest.index[0] - : jobConfig.dest.index; - const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex; - let indexP: IndexPattern | undefined; - - try { - indexP = await mlContext.indexPatterns.get(destIndexPatternId); - } catch (e) { - indexP = undefined; - } - - if (indexP === undefined) { - const sourceIndex = jobConfig.source.index[0]; - const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; - indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); - } - - if (indexP !== undefined) { - setIndexPattern(indexP); - await newJobCapsService.initializeFromIndexPattern(indexP, false, false); - } - setIsInitialized(true); - } catch (e) { - if (e.message !== undefined) { - setJobCapsServiceErrorMessage(e.message); - } else { - setJobCapsServiceErrorMessage(JSON.stringify(e)); - } - } - } - }; - - useEffect(() => { - initializeJobCapsService(); - }, [jobConfig && jobConfig.id]); - - if (jobConfigErrorMessage !== undefined || jobCapsServiceErrorMessage !== undefined) { - return ( - - - - -

{jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}

-
-
- ); - } - return ( - - {isLoadingJobConfig === true && jobConfig === undefined && } - {isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && ( - + - {isLoadingJobConfig === true && jobConfig === undefined && } - {isLoadingJobConfig === false && - jobConfig !== undefined && - indexPattern !== undefined && - isInitialized === true && ( - - )} - + EvaluatePanel={EvaluatePanel} + /> ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration_data_grid.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration_data_grid.tsx deleted file mode 100644 index 424fc002795ca..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration_data_grid.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react'; - -import { i18n } from '@kbn/i18n'; - -import { EuiDataGrid, EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { euiDataGridStyle, euiDataGridToolbarSettings } from '../../../../common'; - -import { mlFieldFormatService } from '../../../../../services/field_format_service'; - -import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; - -const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; - -type Pagination = Pick; -type TableItem = Record; - -interface ExplorationDataGridProps { - colorRange?: (d: number) => string; - columns: any[]; - indexPattern: IndexPattern; - pagination: Pagination; - resultsField: string; - rowCount: number; - selectedFields: string[]; - setPagination: Dispatch>; - setSelectedFields: Dispatch>; - setSortingColumns: Dispatch>; - sortingColumns: EuiDataGridSorting['columns']; - tableItems: TableItem[]; -} - -export const ClassificationExplorationDataGrid: FC = ({ - columns, - indexPattern, - pagination, - resultsField, - rowCount, - selectedFields, - setPagination, - setSelectedFields, - setSortingColumns, - sortingColumns, - tableItems, -}) => { - const renderCellValue = useMemo(() => { - return ({ rowIndex, columnId }: { rowIndex: number; columnId: string; setCellProps: any }) => { - const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; - - const fullItem = tableItems[adjustedRowIndex]; - - if (fullItem === undefined) { - return null; - } - - let format: any; - - if (indexPattern !== undefined) { - format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, ''); - } - - const cellValue = - fullItem.hasOwnProperty(columnId) && fullItem[columnId] !== undefined - ? fullItem[columnId] - : null; - - if (format !== undefined) { - return format.convert(cellValue, 'text'); - } - - if (typeof cellValue === 'string' || cellValue === null) { - return cellValue; - } - - if (typeof cellValue === 'boolean') { - return cellValue ? 'true' : 'false'; - } - - if (typeof cellValue === 'object' && cellValue !== null) { - return JSON.stringify(cellValue); - } - - return cellValue; - }; - }, [resultsField, rowCount, tableItems, pagination.pageIndex, pagination.pageSize]); - - const onChangeItemsPerPage = useCallback( - pageSize => { - setPagination(p => { - const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); - return { pageIndex, pageSize }; - }); - }, - [setPagination] - ); - - const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ - setPagination, - ]); - - const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]); - - return ( - - ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/use_explore_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/use_explore_data.ts deleted file mode 100644 index c8809ca5e471b..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/use_explore_data.ts +++ /dev/null @@ -1,292 +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 { useEffect, useState, Dispatch, SetStateAction } from 'react'; -import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { SearchResponse } from 'elasticsearch'; -import { cloneDeep } from 'lodash'; - -import { SORT_DIRECTION } from '../../../../../components/ml_in_memory_table'; - -import { ml } from '../../../../../services/ml_api_service'; -import { getNestedProperty } from '../../../../../util/object_utils'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; -import { isKeywordAndTextType } from '../../../../common/fields'; -import { Dictionary } from '../../../../../../../common/types/common'; -import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; -import { - defaultSearchQuery, - ResultsSearchQuery, - isResultsSearchBoolQuery, - LoadExploreDataArg, -} from '../../../../common/analytics'; - -import { - getDefaultFieldsFromJobCaps, - getDependentVar, - getFlattenedFields, - getPredictedFieldName, - DataFrameAnalyticsConfig, - EsFieldName, - INDEX_STATUS, -} from '../../../../common'; -import { SavedSearchQuery } from '../../../../../contexts/ml'; - -export type TableItem = Record; -type Pagination = Pick; - -export interface UseExploreDataReturnType { - errorMessage: string; - fieldTypes: { [key: string]: ES_FIELD_TYPES }; - pagination: Pagination; - rowCount: number; - searchQuery: SavedSearchQuery; - selectedFields: EsFieldName[]; - setFilterByIsTraining: Dispatch>; - setPagination: Dispatch>; - setSearchQuery: Dispatch>; - setSelectedFields: Dispatch>; - setSortingColumns: Dispatch>; - sortingColumns: EuiDataGridSorting['columns']; - status: INDEX_STATUS; - tableFields: string[]; - tableItems: TableItem[]; -} - -type EsSorting = Dictionary<{ - order: 'asc' | 'desc'; -}>; - -// The types specified in `@types/elasticsearch` are out of date and still have `total: number`. -interface SearchResponse7 extends SearchResponse { - hits: SearchResponse['hits'] & { - total: { - value: number; - relation: string; - }; - }; -} - -export const useExploreData = ( - jobConfig: DataFrameAnalyticsConfig, - needsDestIndexFields: boolean -): UseExploreDataReturnType => { - const [errorMessage, setErrorMessage] = useState(''); - const [status, setStatus] = useState(INDEX_STATUS.UNUSED); - - const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]); - const [tableFields, setTableFields] = useState([]); - const [tableItems, setTableItems] = useState([]); - const [fieldTypes, setFieldTypes] = useState<{ [key: string]: ES_FIELD_TYPES }>({}); - const [rowCount, setRowCount] = useState(0); - - const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 }); - const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); - const [filterByIsTraining, setFilterByIsTraining] = useState(undefined); - const [sortingColumns, setSortingColumns] = useState([]); - - const predictedFieldName = getPredictedFieldName( - jobConfig.dest.results_field, - jobConfig.analysis - ); - const dependentVariable = getDependentVar(jobConfig.analysis); - - const getDefaultSelectedFields = () => { - const { fields } = newJobCapsService; - if (selectedFields.length === 0 && jobConfig !== undefined) { - const { selectedFields: defaultSelected, docFields } = getDefaultFieldsFromJobCaps( - fields, - jobConfig, - needsDestIndexFields - ); - - const types: { [key: string]: ES_FIELD_TYPES } = {}; - const allFields: string[] = []; - - docFields.forEach(field => { - types[field.id] = field.type; - allFields.push(field.id); - }); - - setFieldTypes(types); - setSelectedFields(defaultSelected.map(field => field.id)); - setTableFields(allFields); - } - }; - - const loadExploreData = async ({ - filterByIsTraining: isTraining, - searchQuery: incomingQuery, - }: LoadExploreDataArg) => { - if (jobConfig !== undefined) { - setErrorMessage(''); - setStatus(INDEX_STATUS.LOADING); - - try { - const resultsField = jobConfig.dest.results_field; - const searchQueryClone: ResultsSearchQuery = cloneDeep(incomingQuery); - let query: ResultsSearchQuery; - const { pageIndex, pageSize } = pagination; - // If filterByIsTraining is defined - add that in to the final query - const trainingQuery = - isTraining !== undefined - ? { - term: { [`${resultsField}.is_training`]: { value: isTraining } }, - } - : undefined; - - if (JSON.stringify(incomingQuery) === JSON.stringify(defaultSearchQuery)) { - const existsQuery = { - exists: { - field: resultsField, - }, - }; - - query = { - bool: { - must: [existsQuery], - }, - }; - - if (trainingQuery !== undefined && isResultsSearchBoolQuery(query)) { - query.bool.must.push(trainingQuery); - } - } else if (isResultsSearchBoolQuery(searchQueryClone)) { - if (searchQueryClone.bool.must === undefined) { - searchQueryClone.bool.must = []; - } - - searchQueryClone.bool.must.push({ - exists: { - field: resultsField, - }, - }); - - if (trainingQuery !== undefined) { - searchQueryClone.bool.must.push(trainingQuery); - } - - query = searchQueryClone; - } else { - query = searchQueryClone; - } - - const sort: EsSorting = sortingColumns - .map(column => { - const { id } = column; - column.id = isKeywordAndTextType(id) ? `${id}.keyword` : id; - return column; - }) - .reduce((s, column) => { - s[column.id] = { order: column.direction }; - return s; - }, {} as EsSorting); - - const resp: SearchResponse7 = await ml.esSearch({ - index: jobConfig.dest.index, - body: { - query, - from: pageIndex * pageSize, - size: pageSize, - ...(Object.keys(sort).length > 0 ? { sort } : {}), - }, - }); - - setRowCount(resp.hits.total.value); - - const docs = resp.hits.hits; - - if (docs.length === 0) { - setTableItems([]); - setStatus(INDEX_STATUS.LOADED); - return; - } - - // Create a version of the doc's source with flattened field names. - // This avoids confusion later on if a field name has dots in its name - // or is a nested fields when displaying it via EuiInMemoryTable. - const flattenedFields = getFlattenedFields(docs[0]._source, resultsField); - const transformedTableItems = docs.map(doc => { - const item: TableItem = {}; - flattenedFields.forEach(ff => { - item[ff] = getNestedProperty(doc._source, ff); - if (item[ff] === undefined) { - // If the attribute is undefined, it means it was not a nested property - // but had dots in its actual name. This selects the property by its - // full name and assigns it to `item[ff]`. - item[ff] = doc._source[`"${ff}"`]; - } - if (item[ff] === undefined) { - const parts = ff.split('.'); - if (parts[0] === resultsField && parts.length >= 2) { - parts.shift(); - if (doc._source[resultsField] !== undefined) { - item[ff] = doc._source[resultsField][parts.join('.')]; - } - } - } - }); - return item; - }); - - setTableItems(transformedTableItems); - setStatus(INDEX_STATUS.LOADED); - } catch (e) { - if (e.message !== undefined) { - setErrorMessage(e.message); - } else { - setErrorMessage(JSON.stringify(e)); - } - setTableItems([]); - setStatus(INDEX_STATUS.ERROR); - } - } - }; - - useEffect(() => { - getDefaultSelectedFields(); - }, [jobConfig && jobConfig.id]); - - // By default set sorting to descending on the prediction field (`_prediction`). - useEffect(() => { - const sortByField = isKeywordAndTextType(dependentVariable) - ? `${predictedFieldName}.keyword` - : predictedFieldName; - const direction = SORT_DIRECTION.DESC; - - setSortingColumns([{ id: sortByField, direction }]); - }, [jobConfig && jobConfig.id]); - - useEffect(() => { - loadExploreData({ filterByIsTraining, searchQuery }); - }, [ - filterByIsTraining, - jobConfig && jobConfig.id, - pagination, - searchQuery, - selectedFields, - sortingColumns, - ]); - - return { - errorMessage, - fieldTypes, - pagination, - searchQuery, - selectedFields, - rowCount, - setFilterByIsTraining, - setPagination, - setSelectedFields, - setSortingColumns, - setSearchQuery, - sortingColumns, - status, - tableItems, - tableFields, - }; -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/error_callout/error_callout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/error_callout/error_callout.tsx index 9765192f0e446..839587c47289a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/error_callout/error_callout.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/error_callout/error_callout.tsx @@ -15,7 +15,7 @@ interface Props { export const ErrorCallout: FC = ({ error }) => { let errorCallout = ( = ({ error }) => { if (error.includes('index_not_found')) { errorCallout = (

- {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.noIndexCalloutBody', { + {i18n.translate('xpack.ml.dataframe.analytics.errorCallout.noIndexCalloutBody', { defaultMessage: 'The query for the index returned no results. Please make sure the destination index exists and contains documents.', })} @@ -46,16 +46,13 @@ export const ErrorCallout: FC = ({ error }) => { // Job was started but no results have been written yet errorCallout = (

- {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.noDataCalloutBody', { + {i18n.translate('xpack.ml.dataframe.analytics.errorCallout.noDataCalloutBody', { defaultMessage: 'The query for the index returned no results. Please make sure the job has completed and the index contains documents.', })} @@ -66,22 +63,16 @@ export const ErrorCallout: FC = ({ error }) => { // query bar syntax is incorrect errorCallout = (

- {i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.queryParsingErrorBody', - { - defaultMessage: - 'The query syntax is invalid and returned no results. Please check the query syntax and try again.', - } - )} + {i18n.translate('xpack.ml.dataframe.analytics.errorCallout.queryParsingErrorBody', { + defaultMessage: + 'The query syntax is invalid and returned no results. Please check the query syntax and try again.', + })}

); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.tsx deleted file mode 100644 index e88bc1cd06f95..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.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 React, { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react'; - -import { i18n } from '@kbn/i18n'; - -import { EuiDataGrid, EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { euiDataGridStyle, euiDataGridToolbarSettings } from '../../../../common'; - -import { mlFieldFormatService } from '../../../../../services/field_format_service'; - -import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; - -const FEATURE_INFLUENCE = 'feature_influence'; -const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; - -type Pagination = Pick; -type TableItem = Record; - -interface ExplorationDataGridProps { - colorRange: (d: number) => string; - columns: any[]; - indexPattern: IndexPattern; - pagination: Pagination; - resultsField: string; - rowCount: number; - selectedFields: string[]; - setPagination: Dispatch>; - setSelectedFields: Dispatch>; - setSortingColumns: Dispatch>; - sortingColumns: EuiDataGridSorting['columns']; - tableItems: TableItem[]; -} - -export const ExplorationDataGrid: FC = ({ - colorRange, - columns, - indexPattern, - pagination, - resultsField, - rowCount, - selectedFields, - setPagination, - setSelectedFields, - setSortingColumns, - sortingColumns, - tableItems, -}) => { - const renderCellValue = useMemo(() => { - return ({ - rowIndex, - columnId, - setCellProps, - }: { - rowIndex: number; - columnId: string; - setCellProps: any; - }) => { - const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; - - const fullItem = tableItems[adjustedRowIndex]; - - if (fullItem === undefined) { - return null; - } - - let format: any; - - if (indexPattern !== undefined) { - format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, ''); - } - - const cellValue = - fullItem.hasOwnProperty(columnId) && fullItem[columnId] !== undefined - ? fullItem[columnId] - : null; - - const split = columnId.split('.'); - let backgroundColor; - - // column with feature values get color coded by its corresponding influencer value - if (fullItem[`${resultsField}.${FEATURE_INFLUENCE}.${columnId}`] !== undefined) { - backgroundColor = colorRange(fullItem[`${resultsField}.${FEATURE_INFLUENCE}.${columnId}`]); - } - - // column with influencer values get color coded by its own value - if (split.length > 2 && split[0] === resultsField && split[1] === FEATURE_INFLUENCE) { - backgroundColor = colorRange(cellValue); - } - - if (backgroundColor !== undefined) { - setCellProps({ - style: { backgroundColor }, - }); - } - - if (format !== undefined) { - return format.convert(cellValue, 'text'); - } - - if (typeof cellValue === 'string' || cellValue === null) { - return cellValue; - } - - if (typeof cellValue === 'boolean') { - return cellValue ? 'true' : 'false'; - } - - if (typeof cellValue === 'object' && cellValue !== null) { - return JSON.stringify(cellValue); - } - - return cellValue; - }; - }, [resultsField, rowCount, tableItems, pagination.pageIndex, pagination.pageSize]); - - const onChangeItemsPerPage = useCallback( - pageSize => { - setPagination(p => { - const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); - return { pageIndex, pageSize }; - }); - }, - [setPagination] - ); - - const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ - setPagination, - ]); - - const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]); - - return ( - - ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx new file mode 100644 index 0000000000000..1986c486974c9 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx @@ -0,0 +1,76 @@ +/* + * 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, useState } from 'react'; + +import { EuiSpacer } from '@elastic/eui'; + +import { useResultsViewConfig, DataFrameAnalyticsConfig } from '../../../../common'; +import { ResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics'; + +import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; + +import { ExplorationResultsTable } from '../exploration_results_table'; +import { JobConfigErrorCallout } from '../job_config_error_callout'; +import { LoadingPanel } from '../loading_panel'; + +export interface EvaluatePanelProps { + jobConfig: DataFrameAnalyticsConfig; + jobStatus?: DATA_FRAME_TASK_STATE; + searchQuery: ResultsSearchQuery; +} + +interface Props { + jobId: string; + title: string; + EvaluatePanel: FC; +} + +export const ExplorationPageWrapper: FC = ({ jobId, title, EvaluatePanel }) => { + const { + indexPattern, + isInitialized, + isLoadingJobConfig, + jobCapsServiceErrorMessage, + jobConfig, + jobConfigErrorMessage, + jobStatus, + } = useResultsViewConfig(jobId); + const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); + + if (jobConfigErrorMessage !== undefined || jobCapsServiceErrorMessage !== undefined) { + return ( + + ); + } + + return ( + <> + {isLoadingJobConfig === true && jobConfig === undefined && } + {isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && ( + + )} + + {isLoadingJobConfig === true && jobConfig === undefined && } + {isLoadingJobConfig === false && + jobConfig !== undefined && + indexPattern !== undefined && + isInitialized === true && ( + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts similarity index 73% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/index.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts index dd896ca02f7f7..bf294a3cd08c9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { useExploreData, TableItem } from './use_explore_data'; +export { EvaluatePanelProps, ExplorationPageWrapper } from './exploration_page_wrapper'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_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 similarity index 57% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index bf63dfe68fe9e..24e5785c6e808 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useEffect } from 'react'; +import React, { Fragment, FC, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCallOut, @@ -12,17 +12,15 @@ import { EuiFlexItem, EuiFormRow, EuiPanel, - EuiProgress, EuiSpacer, EuiText, } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; -import { - BASIC_NUMERICAL_TYPES, - EXTENDED_NUMERICAL_TYPES, - sortRegressionResultsFields, -} from '../../../../common/fields'; + +import { DataGrid } from '../../../../../components/data_grid'; +import { SavedSearchQuery } from '../../../../../contexts/ml'; +import { getToastNotifications } from '../../../../../util/dependency_cache'; import { DataFrameAnalyticsConfig, @@ -33,20 +31,20 @@ import { } from '../../../../common'; import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; -import { useExploreData } from './use_explore_data'; // TableItem -import { ExplorationTitle } from './classification_exploration'; -import { ClassificationExplorationDataGrid } from './classification_exploration_data_grid'; +import { ExplorationTitle } from '../exploration_title'; import { ExplorationQueryBar } from '../exploration_query_bar'; +import { useExplorationResults } from './use_exploration_results'; + const showingDocs = i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.documentsShownHelpText', + 'xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText', { defaultMessage: 'Showing documents for which predictions exist', } ); const showingFirstDocs = i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.firstDocumentsShownHelpText', + 'xpack.ml.dataframe.analytics.explorationResults.firstDocumentsShownHelpText', { defaultMessage: 'Showing first {searchSize} documents for which predictions exist', values: { searchSize: SEARCH_SIZE }, @@ -58,71 +56,22 @@ interface Props { jobConfig: DataFrameAnalyticsConfig; jobStatus?: DATA_FRAME_TASK_STATE; setEvaluateSearchQuery: React.Dispatch>; + title: string; } -export const ResultsTable: FC = React.memo( - ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery }) => { - const needsDestIndexFields = indexPattern && indexPattern.title === jobConfig.source.index[0]; - const resultsField = jobConfig.dest.results_field; - const { - errorMessage, - fieldTypes, - pagination, - searchQuery, - selectedFields, - rowCount, - setPagination, - setSearchQuery, - setSelectedFields, - setSortingColumns, - sortingColumns, - status, - tableFields, - tableItems, - } = useExploreData(jobConfig, needsDestIndexFields); +export const ExplorationResultsTable: FC = React.memo( + ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery, title }) => { + const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); useEffect(() => { setEvaluateSearchQuery(searchQuery); }, [JSON.stringify(searchQuery)]); - const columns = tableFields - .sort((a: any, b: any) => sortRegressionResultsFields(a, b, jobConfig)) - .map((field: any) => { - // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] - // To fall back to the default string schema it needs to be undefined. - let schema; - let isSortable = true; - const type = fieldTypes[field]; - const isNumber = - type !== undefined && - (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type)); - - if (isNumber) { - schema = 'numeric'; - } - - switch (type) { - case 'date': - schema = 'datetime'; - break; - case 'geo_point': - schema = 'json'; - break; - case 'boolean': - schema = 'boolean'; - break; - } + const classificationData = useExplorationResults(indexPattern, jobConfig, searchQuery); + const docFieldsCount = classificationData.columns.length; + const { columns, errorMessage, status, tableItems, visibleColumns } = classificationData; - if (field === `${resultsField}.feature_importance`) { - isSortable = false; - } - - return { id: field, schema, isSortable }; - }); - - const docFieldsCount = tableFields.length; - - if (jobConfig === undefined) { + if (jobConfig === undefined || classificationData === undefined) { return null; } // if it's a searchBar syntax error leave the table visible so they can try again @@ -131,7 +80,7 @@ export const ResultsTable: FC = React.memo( - + {jobStatus !== undefined && ( @@ -156,13 +105,13 @@ export const ResultsTable: FC = React.memo( - + {jobStatus !== undefined && ( @@ -177,11 +126,11 @@ export const ResultsTable: FC = React.memo( {docFieldsCount > MAX_COLUMNS && ( {i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.fieldSelection', + 'xpack.ml.dataframe.analytics.explorationResults.fieldSelection', { defaultMessage: '{selectedFieldsLength, number} of {docFieldsCount, number} {docFieldsCount, plural, one {field} other {fields}} selected', - values: { selectedFieldsLength: selectedFields.length, docFieldsCount }, + values: { selectedFieldsLength: visibleColumns.length, docFieldsCount }, } )} @@ -190,10 +139,6 @@ export const ResultsTable: FC = React.memo( - {status === INDEX_STATUS.LOADING && } - {status !== INDEX_STATUS.LOADING && ( - - )} {(columns.length > 0 || searchQuery !== defaultSearchQuery) && ( @@ -213,18 +158,10 @@ export const ResultsTable: FC = React.memo( - diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/index.ts similarity index 77% rename from x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/index.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/index.ts index a13e678813a00..19308640c8b02 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { SourceIndexPreview } from './source_index_preview'; +export { ExplorationResultsTable } from './exploration_results_table'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts new file mode 100644 index 0000000000000..6f9dc694d8172 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; + +import { EuiDataGridColumn } from '@elastic/eui'; + +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; + +import { + getDataGridSchemasFromFieldTypes, + useDataGrid, + useRenderCellValue, + UseIndexDataReturnType, +} from '../../../../../components/data_grid'; +import { SavedSearchQuery } from '../../../../../contexts/ml'; + +import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common'; +import { DEFAULT_RESULTS_FIELD, FEATURE_IMPORTANCE } from '../../../../common/constants'; +import { sortExplorationResultsFields, ML__ID_COPY } from '../../../../common/fields'; + +export const useExplorationResults = ( + indexPattern: IndexPattern | undefined, + jobConfig: DataFrameAnalyticsConfig | undefined, + searchQuery: SavedSearchQuery +): UseIndexDataReturnType => { + const needsDestIndexFields = + indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0]; + + const columns: EuiDataGridColumn[] = []; + + if (jobConfig !== undefined) { + const resultsField = jobConfig.dest.results_field; + const { fieldTypes } = getIndexFields(jobConfig, needsDestIndexFields); + columns.push( + ...getDataGridSchemasFromFieldTypes(fieldTypes, resultsField).sort((a: any, b: any) => + sortExplorationResultsFields(a.id, b.id, jobConfig) + ) + ); + } + + const dataGrid = useDataGrid( + columns, + 25, + // reduce default selected rows from 20 to 8 for performance reasons. + 8, + // by default, hide feature-importance columns and the doc id copy + d => !d.includes(`.${FEATURE_IMPORTANCE}.`) && d !== ML__ID_COPY + ); + + useEffect(() => { + getIndexData(jobConfig, dataGrid, searchQuery); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); + + const renderCellValue = useRenderCellValue( + indexPattern, + dataGrid.pagination, + dataGrid.tableItems, + jobConfig?.dest.results_field ?? DEFAULT_RESULTS_FIELD + ); + + return { + ...dataGrid, + columns, + renderCellValue, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx new file mode 100644 index 0000000000000..f06c88c73df71 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiTitle } from '@elastic/eui'; + +export const ExplorationTitle: FC<{ title: string }> = ({ title }) => ( + + {title} + +); diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/index.ts similarity index 81% rename from x-pack/plugins/transform/public/app/components/pivot_preview/index.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/index.ts index 049e73d6309fc..b34e61b3b5e76 100644 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { PivotPreview } from './pivot_preview'; +export { ExplorationTitle } from './exploration_title'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/index.ts similarity index 78% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/index.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/index.ts index ea89e91de5046..a5991f4325d12 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ExplorationDataGrid } from './exploration_data_grid'; +export { JobConfigErrorCallout } from './job_config_error_callout'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx new file mode 100644 index 0000000000000..945d6654067c0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiCallOut, EuiPanel, EuiSpacer } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { ExplorationTitle } from '../exploration_title'; + +const jobConfigErrorTitle = i18n.translate('xpack.ml.dataframe.analytics.jobConfig.errorTitle', { + defaultMessage: 'Unable to fetch results. An error occurred loading the job configuration data.', +}); + +const jobCapsErrorTitle = i18n.translate('xpack.ml.dataframe.analytics.jobCaps.errorTitle', { + defaultMessage: "Unable to fetch results. An error occurred loading the index's field data.", +}); + +interface Props { + jobCapsServiceErrorMessage: string | undefined; + jobConfigErrorMessage: string | undefined; + title: string; +} + +export const JobConfigErrorCallout: FC = ({ + jobCapsServiceErrorMessage, + jobConfigErrorMessage, + title, +}) => { + return ( + + + + +

{jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}

+
+
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts similarity index 100% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.test.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts similarity index 51% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts index 48db8e34c934e..bfd3dd33995aa 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts @@ -4,9 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DataFrameAnalyticsConfig } from '../../../../common'; +import { DataGridItem } from '../../../../../components/data_grid'; -const OUTLIER_SCORE = 'outlier_score'; +import { DataFrameAnalyticsConfig } from '../../../../common'; +import { FEATURE_INFLUENCE, OUTLIER_SCORE } from '../../../../common/constants'; export const getOutlierScoreFieldName = (jobConfig: DataFrameAnalyticsConfig) => `${jobConfig.dest.results_field}.${OUTLIER_SCORE}`; + +export const getFeatureCount = (resultsField: string, tableItems: DataGridItem[] = []) => { + if (tableItems.length === 0) { + return 0; + } + + return Object.keys(tableItems[0]).filter(key => + key.includes(`${resultsField}.${FEATURE_INFLUENCE}.`) + ).length; +}; 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 fdcb7d9d237e3..0154f92576c4a 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { useState, FC } from 'react'; import { i18n } from '@kbn/i18n'; @@ -15,127 +15,51 @@ import { EuiHorizontalRule, EuiPanel, EuiSpacer, - EuiTitle, } from '@elastic/eui'; import { useColorRange, - ColorRangeLegend, COLOR_RANGE, COLOR_RANGE_SCALE, } from '../../../../../components/color_range_legend'; +import { ColorRangeLegend } from '../../../../../components/color_range_legend'; +import { DataGrid } from '../../../../../components/data_grid'; +import { SavedSearchQuery } from '../../../../../contexts/ml'; +import { getToastNotifications } from '../../../../../util/dependency_cache'; -import { sortColumns, INDEX_STATUS, defaultSearchQuery } from '../../../../common'; +import { defaultSearchQuery, useResultsViewConfig, INDEX_STATUS } from '../../../../common'; import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; -import { useExploreData, TableItem } from '../../hooks/use_explore_data'; - -import { ExplorationDataGrid } from '../exploration_data_grid'; import { ExplorationQueryBar } from '../exploration_query_bar'; +import { ExplorationTitle } from '../exploration_title'; -const FEATURE_INFLUENCE = 'feature_influence'; +import { getFeatureCount } from './common'; +import { useOutlierData } from './use_outlier_data'; -const ExplorationTitle: FC<{ jobId: string }> = ({ jobId }) => ( - - - {i18n.translate('xpack.ml.dataframe.analytics.exploration.jobIdTitle', { - defaultMessage: 'Outlier detection job ID {jobId}', - values: { jobId }, - })} - - -); +export type TableItem = Record; interface ExplorationProps { jobId: string; } -const getFeatureCount = (resultsField: string, tableItems: TableItem[] = []) => { - if (tableItems.length === 0) { - return 0; - } - - return Object.keys(tableItems[0]).filter(key => - key.includes(`${resultsField}.${FEATURE_INFLUENCE}.`) - ).length; -}; - export const OutlierExploration: FC = React.memo(({ jobId }) => { - const { - errorMessage, - indexPattern, - jobConfig, - jobStatus, - pagination, - searchQuery, - selectedFields, - setPagination, - setSearchQuery, - setSelectedFields, - setSortingColumns, - sortingColumns, - rowCount, - status, - tableFields, - tableItems, - } = useExploreData(jobId); - - const columns = []; - - if ( - jobConfig !== undefined && - indexPattern !== undefined && - selectedFields.length > 0 && - tableItems.length > 0 - ) { - const resultsField = jobConfig.dest.results_field; - const removePrefix = new RegExp(`^${resultsField}\.${FEATURE_INFLUENCE}\.`, 'g'); - columns.push( - ...tableFields.sort(sortColumns(tableItems[0], resultsField)).map(id => { - const idWithoutPrefix = id.replace(removePrefix, ''); - const field = indexPattern.fields.getByName(idWithoutPrefix); - - // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] - // To fall back to the default string schema it needs to be undefined. - let schema; - - switch (field?.type) { - case 'date': - schema = 'datetime'; - break; - case 'geo_point': - schema = 'json'; - break; - case 'number': - schema = 'numeric'; - break; - } - - if (id === `${resultsField}.outlier_score`) { - schema = 'numeric'; - } - - return { id, schema }; - }) - ); - } + const explorationTitle = i18n.translate('xpack.ml.dataframe.analytics.exploration.jobIdTitle', { + defaultMessage: 'Outlier detection job ID {jobId}', + values: { jobId }, + }); - const colorRange = useColorRange( - COLOR_RANGE.BLUE, - COLOR_RANGE_SCALE.INFLUENCER, - jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, tableItems) : 1 - ); + const { indexPattern, jobConfig, jobStatus } = useResultsViewConfig(jobId); + const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); + const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery); - if (jobConfig === undefined || indexPattern === undefined) { - return null; - } + const { columns, errorMessage, status, tableItems } = outlierData; // if it's a searchBar syntax error leave the table visible so they can try again if (status === INDEX_STATUS.ERROR && !errorMessage.includes('parsing_exception')) { return ( - + = React.memo(({ jobId }) = ); } - let tableError = - status === INDEX_STATUS.ERROR && errorMessage.includes('parsing_exception') - ? errorMessage - : undefined; - - if (status === INDEX_STATUS.LOADED && tableItems.length === 0 && tableError === undefined) { - tableError = i18n.translate('xpack.ml.dataframe.analytics.exploration.noDataCalloutBody', { - defaultMessage: - 'The query for the index returned no results. Please make sure the index contains documents and your query is not too restrictive.', - }); - } + const colorRange = useColorRange( + COLOR_RANGE.BLUE, + COLOR_RANGE_SCALE.INFLUENCER, + jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, tableItems) : 1 + ); return ( @@ -170,7 +88,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = gutterSize="s" > - + {jobStatus !== undefined && ( @@ -179,7 +97,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = )}
- {(columns.length > 0 || searchQuery !== defaultSearchQuery) && ( + {(columns.length > 0 || searchQuery !== defaultSearchQuery) && indexPattern !== undefined && ( <> @@ -200,19 +118,10 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = {columns.length > 0 && tableItems.length > 0 && ( - )} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.test.ts new file mode 100644 index 0000000000000..91a5ba2db6908 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DataFrameAnalyticsConfig } from '../../../../common'; + +import { getOutlierScoreFieldName } from './common'; + +describe('Data Frame Analytics: common utils', () => { + test('getOutlierScoreFieldName()', () => { + const jobConfig: DataFrameAnalyticsConfig = { + id: 'the-id', + analysis: { outlier_detection: {} }, + dest: { + index: 'the-dest-index', + results_field: 'the-results-field', + }, + source: { + index: 'the-source-index', + }, + analyzed_fields: { includes: [], excludes: [] }, + model_memory_limit: '50mb', + create_time: 1234, + version: '1.0.0', + }; + + const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); + + expect(outlierScoreFieldName).toMatch('the-results-field.outlier_score'); + }); +}); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts new file mode 100644 index 0000000000000..0d06bc0d43307 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; + +import { EuiDataGridColumn } from '@elastic/eui'; + +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; + +import { + useColorRange, + COLOR_RANGE, + COLOR_RANGE_SCALE, +} from '../../../../../components/color_range_legend'; +import { + getDataGridSchemasFromFieldTypes, + useDataGrid, + useRenderCellValue, + UseIndexDataReturnType, +} from '../../../../../components/data_grid'; +import { SavedSearchQuery } from '../../../../../contexts/ml'; + +import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common'; +import { DEFAULT_RESULTS_FIELD, FEATURE_INFLUENCE } from '../../../../common/constants'; +import { sortExplorationResultsFields, ML__ID_COPY } from '../../../../common/fields'; + +import { getFeatureCount, getOutlierScoreFieldName } from './common'; + +export const useOutlierData = ( + indexPattern: IndexPattern | undefined, + jobConfig: DataFrameAnalyticsConfig | undefined, + searchQuery: SavedSearchQuery +): UseIndexDataReturnType => { + const needsDestIndexFields = + indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0]; + + const columns: EuiDataGridColumn[] = []; + + if (jobConfig !== undefined && indexPattern !== undefined) { + const resultsField = jobConfig.dest.results_field; + const { fieldTypes } = getIndexFields(jobConfig, needsDestIndexFields); + columns.push( + ...getDataGridSchemasFromFieldTypes(fieldTypes, resultsField).sort((a: any, b: any) => + sortExplorationResultsFields(a.id, b.id, jobConfig) + ) + ); + } + + const dataGrid = useDataGrid( + columns, + 25, + // reduce default selected rows from 20 to 8 for performance reasons. + 8, + // by default, hide feature-influence columns and the doc id copy + d => !d.includes(`.${FEATURE_INFLUENCE}.`) && d !== ML__ID_COPY + ); + + // initialize sorting: reverse sort on outlier score column + useEffect(() => { + if (jobConfig !== undefined) { + dataGrid.setSortingColumns([{ id: getOutlierScoreFieldName(jobConfig), direction: 'desc' }]); + } + }, [jobConfig && jobConfig.id]); + + useEffect(() => { + getIndexData(jobConfig, dataGrid, searchQuery); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); + + const colorRange = useColorRange( + COLOR_RANGE.BLUE, + COLOR_RANGE_SCALE.INFLUENCER, + jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, dataGrid.tableItems) : 1 + ); + + const renderCellValue = useRenderCellValue( + indexPattern, + dataGrid.pagination, + dataGrid.tableItems, + jobConfig?.dest.results_field ?? DEFAULT_RESULTS_FIELD, + (columnId, cellValue, fullItem, setCellProps) => { + const resultsField = jobConfig?.dest.results_field ?? ''; + + const split = columnId.split('.'); + let backgroundColor; + + // column with feature values get color coded by its corresponding influencer value + if ( + fullItem[resultsField] !== undefined && + fullItem[resultsField][`${FEATURE_INFLUENCE}.${columnId}`] !== undefined + ) { + backgroundColor = colorRange(fullItem[resultsField][`${FEATURE_INFLUENCE}.${columnId}`]); + } + + // column with influencer values get color coded by its own value + if (split.length > 2 && split[0] === resultsField && split[1] === FEATURE_INFLUENCE) { + backgroundColor = colorRange(cellValue); + } + + if (backgroundColor !== undefined) { + setCellProps({ + style: { backgroundColor }, + }); + } + } + ); + + return { + ...dataGrid, + columns, + renderCellValue, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx index 6ef6666be5ec6..f6e8e0047671f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx @@ -235,7 +235,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-regression-evaluation`} > {i18n.translate( - 'xpack.ml.dataframe.analytics.classificationExploration.regressionDocsLink', + 'xpack.ml.dataframe.analytics.regressionExploration.regressionDocsLink', { defaultMessage: 'Regression evaluation docs ', } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx index bfeca76a2b1c7..36d91f6f41d44 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx @@ -4,174 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useState, useEffect } from 'react'; -import { EuiCallOut, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { ml } from '../../../../../services/ml_api_service'; -import { DataFrameAnalyticsConfig } from '../../../../common'; -import { EvaluatePanel } from './evaluate_panel'; -import { ResultsTable } from './results_table'; -import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; -import { ResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics'; -import { LoadingPanel } from '../loading_panel'; -import { getIndexPatternIdFromName } from '../../../../../util/index_utils'; -import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; -import { useMlContext } from '../../../../../contexts/ml'; -import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics'; +import React, { FC } from 'react'; -export const ExplorationTitle: React.FC<{ jobId: string }> = ({ jobId }) => ( - - - {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.tableJobIdTitle', { - defaultMessage: 'Destination index for regression job ID {jobId}', - values: { jobId }, - })} - - -); +import { i18n } from '@kbn/i18n'; -const jobConfigErrorTitle = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.jobConfigurationFetchError', - { - defaultMessage: - 'Unable to fetch results. An error occurred loading the job configuration data.', - } -); +import { ExplorationPageWrapper } from '../exploration_page_wrapper'; -const jobCapsErrorTitle = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.jobCapsFetchError', - { - defaultMessage: "Unable to fetch results. An error occurred loading the index's field data.", - } -); +import { EvaluatePanel } from './evaluate_panel'; interface Props { jobId: string; } export const RegressionExploration: FC = ({ jobId }) => { - const [jobConfig, setJobConfig] = useState(undefined); - const [jobStatus, setJobStatus] = useState(undefined); - const [indexPattern, setIndexPattern] = useState(undefined); - const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false); - const [isInitialized, setIsInitialized] = useState(false); - const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState(undefined); - const [jobCapsServiceErrorMessage, setJobCapsServiceErrorMessage] = useState( - undefined - ); - const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); - const mlContext = useMlContext(); - - const loadJobConfig = async () => { - setIsLoadingJobConfig(true); - try { - const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId); - const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId); - const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats) - ? analyticsStats.data_frame_analytics[0] - : undefined; - - if (stats !== undefined && stats.state) { - setJobStatus(stats.state); - } - - if ( - Array.isArray(analyticsConfigs.data_frame_analytics) && - analyticsConfigs.data_frame_analytics.length > 0 - ) { - setJobConfig(analyticsConfigs.data_frame_analytics[0]); - setIsLoadingJobConfig(false); - } - } catch (e) { - if (e.message !== undefined) { - setJobConfigErrorMessage(e.message); - } else { - setJobConfigErrorMessage(JSON.stringify(e)); - } - setIsLoadingJobConfig(false); - } - }; - - useEffect(() => { - loadJobConfig(); - }, []); - - const initializeJobCapsService = async () => { - if (jobConfig !== undefined) { - try { - const destIndex = Array.isArray(jobConfig.dest.index) - ? jobConfig.dest.index[0] - : jobConfig.dest.index; - const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex; - let indexP: IIndexPattern | undefined; - - try { - indexP = await mlContext.indexPatterns.get(destIndexPatternId); - } catch (e) { - indexP = undefined; - } - - if (indexP === undefined) { - const sourceIndex = jobConfig.source.index[0]; - const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; - indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); - } - - if (indexP !== undefined) { - setIndexPattern(indexP); - await newJobCapsService.initializeFromIndexPattern(indexP, false, false); - } - setIsInitialized(true); - } catch (e) { - if (e.message !== undefined) { - setJobCapsServiceErrorMessage(e.message); - } else { - setJobCapsServiceErrorMessage(JSON.stringify(e)); - } - } - } - }; - - useEffect(() => { - initializeJobCapsService(); - }, [jobConfig && jobConfig.id]); - - if (jobConfigErrorMessage !== undefined || jobCapsServiceErrorMessage !== undefined) { - return ( - - - - -

{jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}

-
-
- ); - } - return ( - - {isLoadingJobConfig === true && jobConfig === undefined && } - {isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && ( - - )} - - {isLoadingJobConfig === true && jobConfig === undefined && } - {isLoadingJobConfig === false && - jobConfig !== undefined && - indexPattern !== undefined && - isInitialized === true && ( - - )} - + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx deleted file mode 100644 index 0fcb1ed600719..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react'; - -import { i18n } from '@kbn/i18n'; - -import { EuiDataGrid, EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { euiDataGridStyle, euiDataGridToolbarSettings } from '../../../../common'; - -import { mlFieldFormatService } from '../../../../../services/field_format_service'; - -import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; - -const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; - -type Pagination = Pick; -type TableItem = Record; - -interface ExplorationDataGridProps { - colorRange?: (d: number) => string; - columns: any[]; - indexPattern: IndexPattern; - pagination: Pagination; - resultsField: string; - rowCount: number; - selectedFields: string[]; - setPagination: Dispatch>; - setSelectedFields: Dispatch>; - setSortingColumns: Dispatch>; - sortingColumns: EuiDataGridSorting['columns']; - tableItems: TableItem[]; -} - -export const RegressionExplorationDataGrid: FC = ({ - columns, - indexPattern, - pagination, - resultsField, - rowCount, - selectedFields, - setPagination, - setSelectedFields, - setSortingColumns, - sortingColumns, - tableItems, -}) => { - const renderCellValue = useMemo(() => { - return ({ rowIndex, columnId }: { rowIndex: number; columnId: string; setCellProps: any }) => { - const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; - - const fullItem = tableItems[adjustedRowIndex]; - - if (fullItem === undefined) { - return null; - } - - let format: any; - - if (indexPattern !== undefined) { - format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, ''); - } - - const cellValue = - fullItem.hasOwnProperty(columnId) && fullItem[columnId] !== undefined - ? fullItem[columnId] - : null; - - if (format !== undefined) { - return format.convert(cellValue, 'text'); - } - - if (typeof cellValue === 'string' || cellValue === null) { - return cellValue; - } - - if (typeof cellValue === 'boolean') { - return cellValue ? 'true' : 'false'; - } - - if (typeof cellValue === 'object' && cellValue !== null) { - return JSON.stringify(cellValue); - } - - return cellValue; - }; - }, [resultsField, rowCount, tableItems, pagination.pageIndex, pagination.pageSize]); - - const onChangeItemsPerPage = useCallback( - pageSize => { - setPagination(p => { - const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); - return { pageIndex, pageSize }; - }); - }, - [setPagination] - ); - - const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ - setPagination, - ]); - - const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]); - - return ( - - ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx deleted file mode 100644 index 43fa50b2e4df5..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Fragment, FC, useEffect } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiPanel, - EuiProgress, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { - BASIC_NUMERICAL_TYPES, - EXTENDED_NUMERICAL_TYPES, - sortRegressionResultsFields, -} from '../../../../common/fields'; - -import { - DataFrameAnalyticsConfig, - MAX_COLUMNS, - INDEX_STATUS, - SEARCH_SIZE, - defaultSearchQuery, -} from '../../../../common'; -import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; -import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; -import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; - -import { useExploreData } from './use_explore_data'; -import { ExplorationTitle } from './regression_exploration'; -import { RegressionExplorationDataGrid } from './regression_exploration_data_grid'; -import { ExplorationQueryBar } from '../exploration_query_bar'; - -const showingDocs = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.documentsShownHelpText', - { - defaultMessage: 'Showing documents for which predictions exist', - } -); - -const showingFirstDocs = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.firstDocumentsShownHelpText', - { - defaultMessage: 'Showing first {searchSize} documents for which predictions exist', - values: { searchSize: SEARCH_SIZE }, - } -); - -interface Props { - indexPattern: IndexPattern; - jobConfig: DataFrameAnalyticsConfig; - jobStatus?: DATA_FRAME_TASK_STATE; - setEvaluateSearchQuery: React.Dispatch>; -} - -export const ResultsTable: FC = React.memo( - ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery }) => { - const needsDestIndexFields = indexPattern && indexPattern.title === jobConfig.source.index[0]; - const resultsField = jobConfig.dest.results_field; - const { - errorMessage, - fieldTypes, - pagination, - searchQuery, - selectedFields, - rowCount, - setPagination, - setSearchQuery, - setSelectedFields, - setSortingColumns, - sortingColumns, - status, - tableFields, - tableItems, - } = useExploreData(jobConfig, needsDestIndexFields); - - useEffect(() => { - setEvaluateSearchQuery(searchQuery); - }, [JSON.stringify(searchQuery)]); - - const columns = tableFields - .sort((a: any, b: any) => sortRegressionResultsFields(a, b, jobConfig)) - .map((field: any) => { - // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] - // To fall back to the default string schema it needs to be undefined. - let schema; - let isSortable = true; - const type = fieldTypes[field]; - const isNumber = - type !== undefined && - (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type)); - - if (isNumber) { - schema = 'numeric'; - } - - switch (type) { - case 'date': - schema = 'datetime'; - break; - case 'geo_point': - schema = 'json'; - break; - case 'boolean': - schema = 'boolean'; - break; - } - - if (field === `${resultsField}.feature_importance`) { - isSortable = false; - } - - return { id: field, schema, isSortable }; - }); - - const docFieldsCount = tableFields.length; - - if (jobConfig === undefined) { - return null; - } - // if it's a searchBar syntax error leave the table visible so they can try again - if (status === INDEX_STATUS.ERROR && !errorMessage.includes('parsing_exception')) { - return ( - - - - - - {jobStatus !== undefined && ( - - {getTaskStateBadge(jobStatus)} - - )} - - -

{errorMessage}

-
-
- ); - } - - return ( - - - - - - - - {jobStatus !== undefined && ( - - {getTaskStateBadge(jobStatus)} - - )} - - - - - - {docFieldsCount > MAX_COLUMNS && ( - - {i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.fieldSelection', - { - defaultMessage: - '{selectedFieldsLength, number} of {docFieldsCount, number} {docFieldsCount, plural, one {field} other {fields}} selected', - values: { selectedFieldsLength: selectedFields.length, docFieldsCount }, - } - )} - - )} - - - - - {status === INDEX_STATUS.LOADING && } - {status !== INDEX_STATUS.LOADING && ( - - )} - {(columns.length > 0 || searchQuery !== defaultSearchQuery) && ( - - - - - - - - - - - - - - - - - )} - - ); - } -); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts deleted file mode 100644 index 978aafd10de11..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts +++ /dev/null @@ -1,291 +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 { useEffect, useState, Dispatch, SetStateAction } from 'react'; -import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { SearchResponse } from 'elasticsearch'; -import { cloneDeep } from 'lodash'; - -import { ml } from '../../../../../services/ml_api_service'; -import { getNestedProperty } from '../../../../../util/object_utils'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; -import { SORT_DIRECTION } from '../../../../../components/ml_in_memory_table'; -import { SavedSearchQuery } from '../../../../../contexts/ml'; - -import { - getDefaultFieldsFromJobCaps, - getDependentVar, - getFlattenedFields, - getPredictedFieldName, - DataFrameAnalyticsConfig, - EsFieldName, - INDEX_STATUS, -} from '../../../../common'; -import { Dictionary } from '../../../../../../../common/types/common'; -import { isKeywordAndTextType } from '../../../../common/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; -import { - LoadExploreDataArg, - defaultSearchQuery, - ResultsSearchQuery, - isResultsSearchBoolQuery, -} from '../../../../common/analytics'; - -export type TableItem = Record; -type Pagination = Pick; - -export interface UseExploreDataReturnType { - errorMessage: string; - fieldTypes: { [key: string]: ES_FIELD_TYPES }; - pagination: Pagination; - rowCount: number; - searchQuery: SavedSearchQuery; - selectedFields: EsFieldName[]; - setFilterByIsTraining: Dispatch>; - setPagination: Dispatch>; - setSearchQuery: Dispatch>; - setSelectedFields: Dispatch>; - setSortingColumns: Dispatch>; - sortingColumns: EuiDataGridSorting['columns']; - status: INDEX_STATUS; - tableFields: string[]; - tableItems: TableItem[]; -} - -type EsSorting = Dictionary<{ - order: 'asc' | 'desc'; -}>; - -// The types specified in `@types/elasticsearch` are out of date and still have `total: number`. -interface SearchResponse7 extends SearchResponse { - hits: SearchResponse['hits'] & { - total: { - value: number; - relation: string; - }; - }; -} - -export const useExploreData = ( - jobConfig: DataFrameAnalyticsConfig, - needsDestIndexFields: boolean -): UseExploreDataReturnType => { - const [errorMessage, setErrorMessage] = useState(''); - const [status, setStatus] = useState(INDEX_STATUS.UNUSED); - - const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]); - const [tableFields, setTableFields] = useState([]); - const [tableItems, setTableItems] = useState([]); - const [fieldTypes, setFieldTypes] = useState<{ [key: string]: ES_FIELD_TYPES }>({}); - const [rowCount, setRowCount] = useState(0); - - const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 }); - const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); - const [filterByIsTraining, setFilterByIsTraining] = useState(undefined); - const [sortingColumns, setSortingColumns] = useState([]); - - const predictedFieldName = getPredictedFieldName( - jobConfig.dest.results_field, - jobConfig.analysis - ); - const dependentVariable = getDependentVar(jobConfig.analysis); - - const getDefaultSelectedFields = () => { - const { fields } = newJobCapsService; - if (selectedFields.length === 0 && jobConfig !== undefined) { - const { selectedFields: defaultSelected, docFields } = getDefaultFieldsFromJobCaps( - fields, - jobConfig, - needsDestIndexFields - ); - - const types: { [key: string]: ES_FIELD_TYPES } = {}; - const allFields: string[] = []; - - docFields.forEach(field => { - types[field.id] = field.type; - allFields.push(field.id); - }); - - setFieldTypes(types); - setSelectedFields(defaultSelected.map(field => field.id)); - setTableFields(allFields); - } - }; - - const loadExploreData = async ({ - filterByIsTraining: isTraining, - searchQuery: incomingQuery, - }: LoadExploreDataArg) => { - if (jobConfig !== undefined) { - setErrorMessage(''); - setStatus(INDEX_STATUS.LOADING); - - try { - const resultsField = jobConfig.dest.results_field; - const searchQueryClone: ResultsSearchQuery = cloneDeep(incomingQuery); - let query: ResultsSearchQuery; - const { pageIndex, pageSize } = pagination; - // If filterByIsTraining is defined - add that in to the final query - const trainingQuery = - isTraining !== undefined - ? { - term: { [`${resultsField}.is_training`]: { value: isTraining } }, - } - : undefined; - - if (JSON.stringify(incomingQuery) === JSON.stringify(defaultSearchQuery)) { - const existsQuery = { - exists: { - field: resultsField, - }, - }; - - query = { - bool: { - must: [existsQuery], - }, - }; - - if (trainingQuery !== undefined && isResultsSearchBoolQuery(query)) { - query.bool.must.push(trainingQuery); - } - } else if (isResultsSearchBoolQuery(searchQueryClone)) { - if (searchQueryClone.bool.must === undefined) { - searchQueryClone.bool.must = []; - } - - searchQueryClone.bool.must.push({ - exists: { - field: resultsField, - }, - }); - - if (trainingQuery !== undefined) { - searchQueryClone.bool.must.push(trainingQuery); - } - - query = searchQueryClone; - } else { - query = searchQueryClone; - } - - const sort: EsSorting = sortingColumns - .map(column => { - const { id } = column; - column.id = isKeywordAndTextType(id) ? `${id}.keyword` : id; - return column; - }) - .reduce((s, column) => { - s[column.id] = { order: column.direction }; - return s; - }, {} as EsSorting); - - const resp: SearchResponse7 = await ml.esSearch({ - index: jobConfig.dest.index, - body: { - query, - from: pageIndex * pageSize, - size: pageSize, - ...(Object.keys(sort).length > 0 ? { sort } : {}), - }, - }); - - setRowCount(resp.hits.total.value); - - const docs = resp.hits.hits; - - if (docs.length === 0) { - setTableItems([]); - setStatus(INDEX_STATUS.LOADED); - return; - } - - // Create a version of the doc's source with flattened field names. - // This avoids confusion later on if a field name has dots in its name - // or is a nested fields when displaying it via EuiInMemoryTable. - const flattenedFields = getFlattenedFields(docs[0]._source, resultsField); - const transformedTableItems = docs.map(doc => { - const item: TableItem = {}; - flattenedFields.forEach(ff => { - item[ff] = getNestedProperty(doc._source, ff); - if (item[ff] === undefined) { - // If the attribute is undefined, it means it was not a nested property - // but had dots in its actual name. This selects the property by its - // full name and assigns it to `item[ff]`. - item[ff] = doc._source[`"${ff}"`]; - } - if (item[ff] === undefined) { - const parts = ff.split('.'); - if (parts[0] === resultsField && parts.length >= 2) { - parts.shift(); - if (doc._source[resultsField] !== undefined) { - item[ff] = doc._source[resultsField][parts.join('.')]; - } - } - } - }); - return item; - }); - - setTableItems(transformedTableItems); - setStatus(INDEX_STATUS.LOADED); - } catch (e) { - if (e.message !== undefined) { - setErrorMessage(e.message); - } else { - setErrorMessage(JSON.stringify(e)); - } - setTableItems([]); - setStatus(INDEX_STATUS.ERROR); - } - } - }; - - useEffect(() => { - getDefaultSelectedFields(); - }, [jobConfig && jobConfig.id]); - - // By default set sorting to descending on the prediction field (`_prediction`). - useEffect(() => { - const sortByField = isKeywordAndTextType(dependentVariable) - ? `${predictedFieldName}.keyword` - : predictedFieldName; - const direction = SORT_DIRECTION.DESC; - - setSortingColumns([{ id: sortByField, direction }]); - }, [jobConfig && jobConfig.id]); - - useEffect(() => { - loadExploreData({ filterByIsTraining, searchQuery }); - }, [ - filterByIsTraining, - jobConfig && jobConfig.id, - pagination, - searchQuery, - selectedFields, - sortingColumns, - ]); - - return { - errorMessage, - fieldTypes, - pagination, - searchQuery, - selectedFields, - rowCount, - setFilterByIsTraining, - setPagination, - setSelectedFields, - setSortingColumns, - setSearchQuery, - sortingColumns, - status, - tableItems, - tableFields, - }; -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts deleted file mode 100644 index a0a9eb8312499..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts +++ /dev/null @@ -1,267 +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 { useEffect, useState, Dispatch, SetStateAction } from 'react'; -import { SearchResponse } from 'elasticsearch'; - -import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; - -import { Dictionary } from '../../../../../../../common/types/common'; - -import { SavedSearchQuery } from '../../../../../contexts/ml'; -import { ml } from '../../../../../services/ml_api_service'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; -import { getIndexPatternIdFromName } from '../../../../../util/index_utils'; -import { getNestedProperty } from '../../../../../util/object_utils'; -import { useMlContext } from '../../../../../contexts/ml'; -import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics'; - -import { - getDefaultSelectableFields, - getFlattenedFields, - DataFrameAnalyticsConfig, - EsFieldName, - INDEX_STATUS, - MAX_COLUMNS, - defaultSearchQuery, -} from '../../../../common'; -import { isKeywordAndTextType } from '../../../../common/fields'; - -import { getOutlierScoreFieldName } from './common'; -import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; - -export type TableItem = Record; - -type Pagination = Pick; - -interface UseExploreDataReturnType { - errorMessage: string; - indexPattern: IndexPattern | undefined; - jobConfig: DataFrameAnalyticsConfig | undefined; - jobStatus: DATA_FRAME_TASK_STATE | undefined; - pagination: Pagination; - searchQuery: SavedSearchQuery; - selectedFields: EsFieldName[]; - setJobConfig: Dispatch>; - setPagination: Dispatch>; - setSearchQuery: Dispatch>; - setSelectedFields: Dispatch>; - setSortingColumns: Dispatch>; - rowCount: number; - sortingColumns: EuiDataGridSorting['columns']; - status: INDEX_STATUS; - tableFields: string[]; - tableItems: TableItem[]; -} - -type EsSorting = Dictionary<{ - order: 'asc' | 'desc'; -}>; - -// The types specified in `@types/elasticsearch` are out of date and still have `total: number`. -interface SearchResponse7 extends SearchResponse { - hits: SearchResponse['hits'] & { - total: { - value: number; - relation: string; - }; - }; -} - -export const useExploreData = (jobId: string): UseExploreDataReturnType => { - const mlContext = useMlContext(); - - const [indexPattern, setIndexPattern] = useState(undefined); - const [jobConfig, setJobConfig] = useState(undefined); - const [jobStatus, setJobStatus] = useState(undefined); - const [errorMessage, setErrorMessage] = useState(''); - const [status, setStatus] = useState(INDEX_STATUS.UNUSED); - - const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]); - const [tableFields, setTableFields] = useState([]); - const [tableItems, setTableItems] = useState([]); - const [rowCount, setRowCount] = useState(0); - - const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 }); - const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); - const [sortingColumns, setSortingColumns] = useState([]); - - // get analytics configuration - useEffect(() => { - (async function() { - const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId); - const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId); - const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats) - ? analyticsStats.data_frame_analytics[0] - : undefined; - - if (stats !== undefined && stats.state) { - setJobStatus(stats.state); - } - - if ( - Array.isArray(analyticsConfigs.data_frame_analytics) && - analyticsConfigs.data_frame_analytics.length > 0 - ) { - setJobConfig(analyticsConfigs.data_frame_analytics[0]); - } - })(); - }, []); - - // get index pattern and field caps - useEffect(() => { - (async () => { - if (jobConfig !== undefined) { - try { - const destIndex = Array.isArray(jobConfig.dest.index) - ? jobConfig.dest.index[0] - : jobConfig.dest.index; - const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex; - let indexP: IndexPattern | undefined; - - try { - indexP = await mlContext.indexPatterns.get(destIndexPatternId); - } catch (e) { - indexP = undefined; - } - - if (indexP === undefined) { - const sourceIndex = jobConfig.source.index[0]; - const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; - indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); - } - - if (indexP !== undefined) { - setIndexPattern(indexP); - await newJobCapsService.initializeFromIndexPattern(indexP, false, false); - } - } catch (e) { - // eslint-disable-next-line - console.log('Error loading index field data', e); - } - } - })(); - }, [jobConfig && jobConfig.id]); - - // initialize sorting: reverse sort on outlier score column - useEffect(() => { - if (jobConfig !== undefined) { - setSortingColumns([{ id: getOutlierScoreFieldName(jobConfig), direction: 'desc' }]); - } - }, [jobConfig && jobConfig.id]); - - // update data grid data - useEffect(() => { - (async () => { - if (jobConfig !== undefined) { - setErrorMessage(''); - setStatus(INDEX_STATUS.LOADING); - - try { - const resultsField = jobConfig.dest.results_field; - - const sort: EsSorting = sortingColumns - .map(column => { - const { id } = column; - column.id = isKeywordAndTextType(id) ? `${id}.keyword` : id; - return column; - }) - .reduce((s, column) => { - s[column.id] = { order: column.direction }; - return s; - }, {} as EsSorting); - - const { pageIndex, pageSize } = pagination; - const resp: SearchResponse7 = await ml.esSearch({ - index: jobConfig.dest.index, - body: { - query: searchQuery, - from: pageIndex * pageSize, - size: pageSize, - ...(Object.keys(sort).length > 0 ? { sort } : {}), - }, - }); - - setRowCount(resp.hits.total.value); - - const docs = resp.hits.hits; - - if (docs.length === 0) { - setTableItems([]); - setStatus(INDEX_STATUS.LOADED); - return; - } - - if (selectedFields.length === 0) { - const newSelectedFields = getDefaultSelectableFields(docs, resultsField); - setSelectedFields(newSelectedFields.sort().splice(0, MAX_COLUMNS)); - } - - // Create a version of the doc's source with flattened field names. - // This avoids confusion later on if a field name has dots in its name - // or is a nested fields when displaying it via EuiInMemoryTable. - const flattenedFields = getFlattenedFields(docs[0]._source, resultsField); - const transformedTableItems = docs.map(doc => { - const item: TableItem = {}; - flattenedFields.forEach(ff => { - item[ff] = getNestedProperty(doc._source, ff); - if (item[ff] === undefined) { - // If the attribute is undefined, it means it was not a nested property - // but had dots in its actual name. This selects the property by its - // full name and assigns it to `item[ff]`. - item[ff] = doc._source[`"${ff}"`]; - } - if (item[ff] === undefined) { - const parts = ff.split('.'); - if (parts[0] === resultsField && parts.length >= 2) { - parts.shift(); - if (doc._source[resultsField] !== undefined) { - item[ff] = doc._source[resultsField][parts.join('.')]; - } - } - } - }); - return item; - }); - - setTableFields(flattenedFields); - setTableItems(transformedTableItems); - setStatus(INDEX_STATUS.LOADED); - } catch (e) { - if (e.message !== undefined) { - setErrorMessage(e.message); - } else { - setErrorMessage(JSON.stringify(e)); - } - setTableItems([]); - setStatus(INDEX_STATUS.ERROR); - } - } - })(); - }, [jobConfig && jobConfig.id, pagination, searchQuery, selectedFields, sortingColumns]); - - return { - errorMessage, - indexPattern, - jobConfig, - jobStatus, - pagination, - rowCount, - searchQuery, - selectedFields, - setJobConfig, - setPagination, - setSearchQuery, - setSelectedFields, - setSortingColumns, - sortingColumns, - status, - tableFields, - tableItems, - }; -}; 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 eb1871c98764b..8c65af1d92959 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 @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { DeepReadonly } from '../../../../../../../common/types/common'; import { DataFrameAnalyticsConfig, isOutlierAnalysis } from '../../../../common'; import { isClassificationAnalysis, isRegressionAnalysis } from '../../../../common/analytics'; +import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants'; import { CreateAnalyticsFormProps, DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES, @@ -214,7 +215,7 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo }, results_field: { optional: true, - defaultValue: 'ml', + defaultValue: DEFAULT_RESULTS_FIELD, }, }, model_memory_limit: { diff --git a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts index 0db11ffa061c0..9d8106a1366d6 100644 --- a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +// actual mocks export const expandLiteralStrings = jest.fn(); export const XJsonMode = jest.fn(); export const useRequest = jest.fn(() => ({ @@ -11,5 +12,20 @@ export const useRequest = jest.fn(() => ({ error: null, data: undefined, })); -export { mlInMemoryTableBasicFactory } from '../../../ml/public/application/components/ml_in_memory_table'; -export const SORT_DIRECTION = { ASC: 'asc' }; + +// just passing through the reimports +export { getErrorMessage } from '../../../ml/common/util/errors'; +export { + getDataGridSchemaFromKibanaFieldType, + getFieldsFromKibanaIndexPattern, + multiColumnSortFactory, + useDataGrid, + useRenderCellValue, + DataGrid, + EsSorting, + RenderCellValue, + SearchResponse7, + UseDataGridReturnType, + UseIndexDataReturnType, +} from '../../../ml/public/application/components/data_grid'; +export { INDEX_STATUS } from '../../../ml/public/application/data_frame_analytics/common'; diff --git a/x-pack/plugins/transform/public/app/common/data_grid.test.ts b/x-pack/plugins/transform/public/app/common/data_grid.test.ts new file mode 100644 index 0000000000000..0e5ecb5d3b214 --- /dev/null +++ b/x-pack/plugins/transform/public/app/common/data_grid.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + getPreviewRequestBody, + PivotAggsConfig, + PivotGroupByConfig, + PIVOT_SUPPORTED_AGGS, + PIVOT_SUPPORTED_GROUP_BY_AGGS, + SimpleQuery, +} from '../common'; + +import { getIndexDevConsoleStatement, getPivotPreviewDevConsoleStatement } from './data_grid'; + +describe('Transform: Data Grid', () => { + test('getPivotPreviewDevConsoleStatement()', () => { + const query: SimpleQuery = { + query_string: { + query: '*', + default_operator: 'AND', + }, + }; + const groupBy: PivotGroupByConfig = { + agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, + field: 'the-group-by-field', + aggName: 'the-group-by-agg-name', + dropDownName: 'the-group-by-drop-down-name', + }; + const agg: PivotAggsConfig = { + agg: PIVOT_SUPPORTED_AGGS.AVG, + field: 'the-agg-field', + aggName: 'the-agg-agg-name', + dropDownName: 'the-agg-drop-down-name', + }; + const request = getPreviewRequestBody('the-index-pattern-title', query, [groupBy], [agg]); + const pivotPreviewDevConsoleStatement = getPivotPreviewDevConsoleStatement(request); + + expect(pivotPreviewDevConsoleStatement).toBe(`POST _transform/_preview +{ + "source": { + "index": [ + "the-index-pattern-title" + ] + }, + "pivot": { + "group_by": { + "the-group-by-agg-name": { + "terms": { + "field": "the-group-by-field" + } + } + }, + "aggregations": { + "the-agg-agg-name": { + "avg": { + "field": "the-agg-field" + } + } + } + } +} +`); + }); +}); + +describe('Transform: Index Preview Common', () => { + test('getIndexDevConsoleStatement()', () => { + const query: SimpleQuery = { + query_string: { + query: '*', + default_operator: 'AND', + }, + }; + const indexPreviewDevConsoleStatement = getIndexDevConsoleStatement( + query, + 'the-index-pattern-title' + ); + + expect(indexPreviewDevConsoleStatement).toBe(`GET the-index-pattern-title/_search +{ + "query": { + "query_string": { + "query": "*", + "default_operator": "AND" + } + } +} +`); + }); +}); diff --git a/x-pack/plugins/transform/public/app/common/data_grid.ts b/x-pack/plugins/transform/public/app/common/data_grid.ts index 0e9cceefb3156..cf9ba5d6f5853 100644 --- a/x-pack/plugins/transform/public/app/common/data_grid.ts +++ b/x-pack/plugins/transform/public/app/common/data_grid.ts @@ -4,22 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiDataGridStyle } from '@elastic/eui'; +import { PivotQuery } from './request'; +import { PreviewRequestBody } from './transform'; export const INIT_MAX_COLUMNS = 20; -export const euiDataGridStyle: EuiDataGridStyle = { - border: 'all', - fontSize: 's', - cellPadding: 's', - stripes: false, - rowHover: 'highlight', - header: 'shade', +export const getPivotPreviewDevConsoleStatement = (request: PreviewRequestBody) => { + return `POST _transform/_preview\n${JSON.stringify(request, null, 2)}\n`; }; -export const euiDataGridToolbarSettings = { - showColumnSelector: true, - showStyleSelector: false, - showSortSelector: true, - showFullScreenSelector: false, +export const getIndexDevConsoleStatement = (query: PivotQuery, indexPatternTitle: string) => { + return `GET ${indexPatternTitle}/_search\n${JSON.stringify( + { + query, + }, + null, + 2 + )}\n`; }; diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts index daeddaa801828..009c8c7a2a9f5 100644 --- a/x-pack/plugins/transform/public/app/common/index.ts +++ b/x-pack/plugins/transform/public/app/common/index.ts @@ -5,7 +5,11 @@ */ export { AggName, isAggName } from './aggregations'; -export { euiDataGridStyle, euiDataGridToolbarSettings, INIT_MAX_COLUMNS } from './data_grid'; +export { + getIndexDevConsoleStatement, + getPivotPreviewDevConsoleStatement, + INIT_MAX_COLUMNS, +} from './data_grid'; export { getDefaultSelectableFields, getFlattenedFields, diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/common.ts b/x-pack/plugins/transform/public/app/components/pivot_preview/common.ts deleted file mode 100644 index 498c3a3ac60af..0000000000000 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/common.ts +++ /dev/null @@ -1,60 +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 { EuiDataGridSorting } from '@elastic/eui'; - -import { getNestedProperty } from '../../../../common/utils/object_utils'; - -import { PreviewRequestBody } from '../../common'; - -/** - * Helper to sort an array of objects based on an EuiDataGrid sorting configuration. - * `sortFn()` is recursive to support sorting on multiple columns. - * - * @param sortingColumns - The EUI data grid sorting configuration - * @returns The sorting function which can be used with an array's sort() function. - */ -export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['columns']) => { - const isString = (arg: any): arg is string => { - return typeof arg === 'string'; - }; - - const sortFn = (a: any, b: any, sortingColumnIndex = 0): number => { - const sort = sortingColumns[sortingColumnIndex]; - const aValue = getNestedProperty(a, sort.id, null); - const bValue = getNestedProperty(b, sort.id, null); - - if (typeof aValue === 'number' && typeof bValue === 'number') { - if (aValue < bValue) { - return sort.direction === 'asc' ? -1 : 1; - } - if (aValue > bValue) { - return sort.direction === 'asc' ? 1 : -1; - } - } - - if (isString(aValue) && isString(bValue)) { - if (aValue.localeCompare(bValue) === -1) { - return sort.direction === 'asc' ? -1 : 1; - } - if (aValue.localeCompare(bValue) === 1) { - return sort.direction === 'asc' ? 1 : -1; - } - } - - if (sortingColumnIndex + 1 < sortingColumns.length) { - return sortFn(a, b, sortingColumnIndex + 1); - } - - return 0; - }; - - return sortFn; -}; - -export const getPivotPreviewDevConsoleStatement = (request: PreviewRequestBody) => { - return `POST _transform/_preview\n${JSON.stringify(request, null, 2)}\n`; -}; diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx deleted file mode 100644 index 5ed50eaab46ba..0000000000000 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render, wait } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; - -import { - getPivotQuery, - PivotAggsConfig, - PivotGroupByConfig, - PIVOT_SUPPORTED_AGGS, - PIVOT_SUPPORTED_GROUP_BY_AGGS, -} from '../../common'; - -import { PivotPreview } from './pivot_preview'; - -jest.mock('../../../shared_imports'); -jest.mock('../../../app/app_dependencies'); - -describe('Transform: ', () => { - // Using the async/await wait()/done() pattern to avoid act() errors. - test('Minimal initialization', async done => { - // Arrange - const groupBy: PivotGroupByConfig = { - agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, - field: 'the-group-by-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', - }; - const agg: PivotAggsConfig = { - agg: PIVOT_SUPPORTED_AGGS.AVG, - field: 'the-agg-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', - }; - const props = { - aggs: { 'the-agg-name': agg }, - groupBy: { 'the-group-by-name': groupBy }, - indexPatternTitle: 'the-index-pattern-title', - query: getPivotQuery('the-query'), - }; - - const { getByText } = render(); - - // Act - // Assert - expect(getByText('Transform pivot preview')).toBeInTheDocument(); - await wait(); - done(); - }); -}); diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx deleted file mode 100644 index c50df0366d698..0000000000000 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx +++ /dev/null @@ -1,345 +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 moment from 'moment-timezone'; -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; - -import { i18n } from '@kbn/i18n'; - -import { - EuiButtonIcon, - EuiCallOut, - EuiCodeBlock, - EuiCopy, - EuiDataGrid, - EuiDataGridSorting, - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiTitle, -} from '@elastic/eui'; - -import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/common'; - -import { dictionaryToArray } from '../../../../common/types/common'; -import { formatHumanReadableDateTimeSeconds } from '../../../../common/utils/date_utils'; -import { getNestedProperty } from '../../../../common/utils/object_utils'; - -import { - euiDataGridStyle, - euiDataGridToolbarSettings, - EsFieldName, - PreviewRequestBody, - PivotAggsConfigDict, - PivotGroupByConfig, - PivotGroupByConfigDict, - PivotQuery, - INIT_MAX_COLUMNS, -} from '../../common'; -import { SearchItems } from '../../hooks/use_search_items'; - -import { getPivotPreviewDevConsoleStatement, multiColumnSortFactory } from './common'; -import { PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data'; - -function sortColumns(groupByArr: PivotGroupByConfig[]) { - return (a: string, b: string) => { - // make sure groupBy fields are always most left columns - if (groupByArr.some(d => d.aggName === a) && groupByArr.some(d => d.aggName === b)) { - return a.localeCompare(b); - } - if (groupByArr.some(d => d.aggName === a)) { - return -1; - } - if (groupByArr.some(d => d.aggName === b)) { - return 1; - } - return a.localeCompare(b); - }; -} - -interface PreviewTitleProps { - previewRequest: PreviewRequestBody; -} - -const PreviewTitle: FC = ({ previewRequest }) => { - const euiCopyText = i18n.translate('xpack.transform.pivotPreview.copyClipboardTooltip', { - defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.', - }); - - return ( - - - - - {i18n.translate('xpack.transform.pivotPreview.PivotPreviewTitle', { - defaultMessage: 'Transform pivot preview', - })} - - - - - - {(copy: () => void) => ( - - )} - - - - ); -}; - -interface ErrorMessageProps { - message: string; -} - -const ErrorMessage: FC = ({ message }) => ( - - {message} - -); - -interface PivotPreviewProps { - aggs: PivotAggsConfigDict; - groupBy: PivotGroupByConfigDict; - indexPatternTitle: SearchItems['indexPattern']['title']; - query: PivotQuery; - showHeader?: boolean; -} - -const defaultPagination = { pageIndex: 0, pageSize: 5 }; - -export const PivotPreview: FC = React.memo( - ({ aggs, groupBy, indexPatternTitle, query, showHeader = true }) => { - const { - previewData: data, - previewMappings, - errorMessage, - previewRequest, - status, - } = usePivotPreviewData(indexPatternTitle, query, aggs, groupBy); - const groupByArr = dictionaryToArray(groupBy); - - // Filters mapping properties of type `object`, which get returned for nested field parents. - const columnKeys = Object.keys(previewMappings.properties).filter( - key => previewMappings.properties[key].type !== 'object' - ); - columnKeys.sort(sortColumns(groupByArr)); - - // Column visibility - const [visibleColumns, setVisibleColumns] = useState([]); - - useEffect(() => { - setVisibleColumns(columnKeys.splice(0, INIT_MAX_COLUMNS)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [columnKeys.join()]); - - const [pagination, setPagination] = useState(defaultPagination); - - // Reset pagination if data changes. This is to avoid ending up with an empty table - // when for example the user selected a page that is not available with the updated data. - useEffect(() => { - setPagination(defaultPagination); - }, [data.length]); - - // EuiDataGrid State - const dataGridColumns = columnKeys.map(id => { - const field = previewMappings.properties[id]; - - // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] - // To fall back to the default string schema it needs to be undefined. - let schema; - - switch (field?.type) { - case ES_FIELD_TYPES.GEO_POINT: - case ES_FIELD_TYPES.GEO_SHAPE: - schema = 'json'; - break; - case ES_FIELD_TYPES.BOOLEAN: - schema = 'boolean'; - break; - case ES_FIELD_TYPES.DATE: - case ES_FIELD_TYPES.DATE_NANOS: - schema = 'datetime'; - break; - case ES_FIELD_TYPES.BYTE: - case ES_FIELD_TYPES.DOUBLE: - case ES_FIELD_TYPES.FLOAT: - case ES_FIELD_TYPES.HALF_FLOAT: - case ES_FIELD_TYPES.INTEGER: - case ES_FIELD_TYPES.LONG: - case ES_FIELD_TYPES.SCALED_FLOAT: - case ES_FIELD_TYPES.SHORT: - schema = 'numeric'; - break; - // keep schema undefined for text based columns - case ES_FIELD_TYPES.KEYWORD: - case ES_FIELD_TYPES.TEXT: - break; - } - - return { id, schema }; - }); - - const onChangeItemsPerPage = useCallback( - pageSize => { - setPagination(p => { - const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); - return { pageIndex, pageSize }; - }); - }, - [setPagination] - ); - - const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ - setPagination, - ]); - - // Sorting config - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]); - - if (sortingColumns.length > 0) { - data.sort(multiColumnSortFactory(sortingColumns)); - } - - const pageData = data.slice( - pagination.pageIndex * pagination.pageSize, - (pagination.pageIndex + 1) * pagination.pageSize - ); - - const renderCellValue = useMemo(() => { - return ({ - rowIndex, - columnId, - setCellProps, - }: { - rowIndex: number; - columnId: string; - setCellProps: any; - }) => { - const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; - - const cellValue = pageData.hasOwnProperty(adjustedRowIndex) - ? getNestedProperty(pageData[adjustedRowIndex], columnId, null) - : null; - - if (typeof cellValue === 'object' && cellValue !== null) { - return JSON.stringify(cellValue); - } - - if (cellValue === undefined || cellValue === null) { - return null; - } - - if ( - [ES_FIELD_TYPES.DATE, ES_FIELD_TYPES.DATE_NANOS].includes( - previewMappings.properties[columnId].type - ) - ) { - return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); - } - - if (previewMappings.properties[columnId].type === ES_FIELD_TYPES.BOOLEAN) { - return cellValue ? 'true' : 'false'; - } - - return cellValue; - }; - }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappings.properties]); - - if (status === PIVOT_PREVIEW_STATUS.ERROR) { - return ( -
- - - - -
- ); - } - - if (data.length === 0) { - let noDataMessage = i18n.translate( - 'xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', - { - defaultMessage: - 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', - } - ); - - const aggsArr = dictionaryToArray(aggs); - if (aggsArr.length === 0 || groupByArr.length === 0) { - noDataMessage = i18n.translate( - 'xpack.transform.pivotPreview.PivotPreviewIncompleteConfigCalloutBody', - { - defaultMessage: 'Please choose at least one group-by field and aggregation.', - } - ); - } - - return ( -
- - -

{noDataMessage}

-
-
- ); - } - - if (columnKeys.length === 0) { - return null; - } - - return ( -
- {showHeader && ( - <> - -
- {status === PIVOT_PREVIEW_STATUS.LOADING && } - {status !== PIVOT_PREVIEW_STATUS.LOADING && ( - - )} -
- - )} - {dataGridColumns.length > 0 && data.length > 0 && ( - - )} -
- ); - } -); diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.test.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.test.tsx deleted file mode 100644 index 8d09d06b1c731..0000000000000 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; -import ReactDOM from 'react-dom'; - -import { SimpleQuery } from '../../common'; -import { - PIVOT_PREVIEW_STATUS, - usePivotPreviewData, - UsePivotPreviewDataReturnType, -} from './use_pivot_preview_data'; - -jest.mock('../../hooks/use_api'); - -type Callback = () => void; -interface TestHookProps { - callback: Callback; -} - -const TestHook: FC = ({ callback }) => { - callback(); - return null; -}; - -const testHook = (callback: Callback) => { - const container = document.createElement('div'); - document.body.appendChild(container); - ReactDOM.render(, container); -}; - -const query: SimpleQuery = { - query_string: { - query: '*', - default_operator: 'AND', - }, -}; - -let pivotPreviewObj: UsePivotPreviewDataReturnType; - -describe('usePivotPreviewData', () => { - test('indexPattern not defined', () => { - testHook(() => { - pivotPreviewObj = usePivotPreviewData('the-title', query, {}, {}); - }); - - expect(pivotPreviewObj.errorMessage).toBe(''); - expect(pivotPreviewObj.status).toBe(PIVOT_PREVIEW_STATUS.UNUSED); - expect(pivotPreviewObj.previewData).toEqual([]); - }); - - test('indexPattern set triggers loading', () => { - testHook(() => { - pivotPreviewObj = usePivotPreviewData('the-title', query, {}, {}); - }); - - expect(pivotPreviewObj.errorMessage).toBe(''); - // ideally this should be LOADING instead of UNUSED but jest/enzyme/hooks doesn't - // trigger that state upate yet. - expect(pivotPreviewObj.status).toBe(PIVOT_PREVIEW_STATUS.UNUSED); - expect(pivotPreviewObj.previewData).toEqual([]); - }); - - // TODO add more tests to check data retrieved via `api.esSearch()`. - // This needs more investigation in regards to jest/enzyme's React Hooks support. -}); diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.ts b/x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.ts deleted file mode 100644 index 83fa7ba189ff0..0000000000000 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/use_pivot_preview_data.ts +++ /dev/null @@ -1,91 +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 { useEffect, useState } from 'react'; - -import { dictionaryToArray } from '../../../../common/types/common'; -import { useApi } from '../../hooks/use_api'; - -import { IndexPattern } from '../../../../../../../src/plugins/data/public'; - -import { - getPreviewRequestBody, - PreviewRequestBody, - PivotAggsConfigDict, - PivotGroupByConfigDict, - PivotQuery, - PreviewData, - PreviewMappings, -} from '../../common'; - -export enum PIVOT_PREVIEW_STATUS { - UNUSED, - LOADING, - LOADED, - ERROR, -} - -export interface UsePivotPreviewDataReturnType { - errorMessage: string; - status: PIVOT_PREVIEW_STATUS; - previewData: PreviewData; - previewMappings: PreviewMappings; - previewRequest: PreviewRequestBody; -} - -export const usePivotPreviewData = ( - indexPatternTitle: IndexPattern['title'], - query: PivotQuery, - aggs: PivotAggsConfigDict, - groupBy: PivotGroupByConfigDict -): UsePivotPreviewDataReturnType => { - const [errorMessage, setErrorMessage] = useState(''); - const [status, setStatus] = useState(PIVOT_PREVIEW_STATUS.UNUSED); - const [previewData, setPreviewData] = useState([]); - const [previewMappings, setPreviewMappings] = useState({ properties: {} }); - const api = useApi(); - - const aggsArr = dictionaryToArray(aggs); - const groupByArr = dictionaryToArray(groupBy); - - const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr); - - const getPreviewData = async () => { - if (aggsArr.length === 0 || groupByArr.length === 0) { - setPreviewData([]); - return; - } - - setErrorMessage(''); - setStatus(PIVOT_PREVIEW_STATUS.LOADING); - - try { - const resp = await api.getTransformsPreview(previewRequest); - setPreviewData(resp.preview); - setPreviewMappings(resp.generated_dest_index.mappings); - setStatus(PIVOT_PREVIEW_STATUS.LOADED); - } catch (e) { - setErrorMessage(JSON.stringify(e, null, 2)); - setPreviewData([]); - setPreviewMappings({ properties: {} }); - setStatus(PIVOT_PREVIEW_STATUS.ERROR); - } - }; - - useEffect(() => { - getPreviewData(); - // custom comparison - /* eslint-disable react-hooks/exhaustive-deps */ - }, [ - indexPatternTitle, - JSON.stringify(aggsArr), - JSON.stringify(groupByArr), - JSON.stringify(query), - /* eslint-enable react-hooks/exhaustive-deps */ - ]); - - return { errorMessage, status, previewData, previewMappings, previewRequest }; -}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx new file mode 100644 index 0000000000000..4ca536e3c115d --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx @@ -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 React from 'react'; + +import { render, wait } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; +import '@testing-library/jest-dom/extend-expect'; + +import { CoreSetup } from 'src/core/public'; + +import { DataGrid, UseIndexDataReturnType, INDEX_STATUS } from '../../shared_imports'; + +import { SimpleQuery } from '../common'; + +import { SearchItems } from './use_search_items'; +import { useIndexData } from './use_index_data'; + +jest.mock('../../shared_imports'); +jest.mock('../app_dependencies'); +jest.mock('./use_api'); + +const query: SimpleQuery = { + query_string: { + query: '*', + default_operator: 'AND', + }, +}; + +describe('Transform: useIndexData()', () => { + test('indexPattern set triggers loading', async done => { + const { result, waitForNextUpdate } = renderHook(() => + useIndexData( + ({ + id: 'the-id', + title: 'the-title', + fields: [], + } as unknown) as SearchItems['indexPattern'], + query + ) + ); + const IndexObj: UseIndexDataReturnType = result.current; + + await waitForNextUpdate(); + + expect(IndexObj.errorMessage).toBe(''); + expect(IndexObj.status).toBe(INDEX_STATUS.LOADING); + expect(IndexObj.tableItems).toEqual([]); + done(); + }); +}); + +describe('Transform: with useIndexData()', () => { + // Using the async/await wait()/done() pattern to avoid act() errors. + test('Minimal initialization', async done => { + // Arrange + const indexPattern = { + title: 'the-index-pattern-title', + fields: [] as any[], + } as SearchItems['indexPattern']; + + const Wrapper = () => { + const props = { + ...useIndexData(indexPattern, { match_all: {} }), + copyToClipboard: 'the-copy-to-clipboard-code', + copyToClipboardDescription: 'the-copy-to-clipboard-description', + dataTestSubj: 'the-data-test-subj', + title: 'the-index-preview-title', + toastNotifications: {} as CoreSetup['notifications']['toasts'], + }; + + return ; + }; + const { getByText } = render(); + + // Act + // Assert + expect(getByText('the-index-preview-title')).toBeInTheDocument(); + await wait(); + done(); + }); +}); diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts new file mode 100644 index 0000000000000..ec5a4d244c152 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; + +import { + getDataGridSchemaFromKibanaFieldType, + getFieldsFromKibanaIndexPattern, + getErrorMessage, + useDataGrid, + useRenderCellValue, + EsSorting, + SearchResponse7, + UseIndexDataReturnType, + INDEX_STATUS, +} from '../../shared_imports'; + +import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common'; + +import { SearchItems } from './use_search_items'; +import { useApi } from './use_api'; + +type IndexSearchResponse = SearchResponse7; + +export const useIndexData = ( + indexPattern: SearchItems['indexPattern'], + query: PivotQuery +): UseIndexDataReturnType => { + const api = useApi(); + + const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern); + + // EuiDataGrid State + const columns = [ + ...indexPatternFields.map(id => { + const field = indexPattern.fields.getByName(id); + const schema = getDataGridSchemaFromKibanaFieldType(field); + return { id, schema }; + }), + ]; + + const dataGrid = useDataGrid(columns); + + const { + pagination, + resetPagination, + setErrorMessage, + setRowCount, + setStatus, + setTableItems, + sortingColumns, + tableItems, + } = dataGrid; + + useEffect(() => { + resetPagination(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(query)]); + + const getIndexData = async function() { + setErrorMessage(''); + setStatus(INDEX_STATUS.LOADING); + + const sort: EsSorting = sortingColumns.reduce((s, column) => { + s[column.id] = { order: column.direction }; + return s; + }, {} as EsSorting); + + const esSearchRequest = { + index: indexPattern.title, + body: { + // Instead of using the default query (`*`), fall back to a more efficient `match_all` query. + query: isDefaultQuery(query) ? matchAllQuery : query, + from: pagination.pageIndex * pagination.pageSize, + size: pagination.pageSize, + ...(Object.keys(sort).length > 0 ? { sort } : {}), + }, + }; + + try { + const resp: IndexSearchResponse = await api.esSearch(esSearchRequest); + + const docs = resp.hits.hits.map(d => d._source); + + setRowCount(resp.hits.total.value); + setTableItems(docs); + setStatus(INDEX_STATUS.LOADED); + } catch (e) { + setErrorMessage(getErrorMessage(e)); + setStatus(INDEX_STATUS.ERROR); + } + }; + + useEffect(() => { + getIndexData(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); + + const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems); + + return { + ...dataGrid, + columns, + renderCellValue, + }; +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts new file mode 100644 index 0000000000000..ff7ca5d42b5f7 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; +import { useEffect, useMemo, useState } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; + +import { dictionaryToArray } from '../../../common/types/common'; +import { formatHumanReadableDateTimeSeconds } from '../../../common/utils/date_utils'; +import { getNestedProperty } from '../../../common/utils/object_utils'; + +import { + getErrorMessage, + multiColumnSortFactory, + useDataGrid, + RenderCellValue, + UseIndexDataReturnType, + INDEX_STATUS, +} from '../../shared_imports'; + +import { + getPreviewRequestBody, + PivotAggsConfigDict, + PivotGroupByConfigDict, + PivotGroupByConfig, + PivotQuery, + PreviewMappings, +} from '../common'; + +import { SearchItems } from './use_search_items'; +import { useApi } from './use_api'; + +function sortColumns(groupByArr: PivotGroupByConfig[]) { + return (a: string, b: string) => { + // make sure groupBy fields are always most left columns + if (groupByArr.some(d => d.aggName === a) && groupByArr.some(d => d.aggName === b)) { + return a.localeCompare(b); + } + if (groupByArr.some(d => d.aggName === a)) { + return -1; + } + if (groupByArr.some(d => d.aggName === b)) { + return 1; + } + return a.localeCompare(b); + }; +} + +export const usePivotData = ( + indexPatternTitle: SearchItems['indexPattern']['title'], + query: PivotQuery, + aggs: PivotAggsConfigDict, + groupBy: PivotGroupByConfigDict +): UseIndexDataReturnType => { + const [previewMappings, setPreviewMappings] = useState({ properties: {} }); + const api = useApi(); + + const aggsArr = dictionaryToArray(aggs); + const groupByArr = dictionaryToArray(groupBy); + + // Filters mapping properties of type `object`, which get returned for nested field parents. + const columnKeys = Object.keys(previewMappings.properties).filter( + key => previewMappings.properties[key].type !== 'object' + ); + columnKeys.sort(sortColumns(groupByArr)); + + // EuiDataGrid State + const columns = columnKeys.map(id => { + const field = previewMappings.properties[id]; + + // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] + // To fall back to the default string schema it needs to be undefined. + let schema; + + switch (field?.type) { + case ES_FIELD_TYPES.GEO_POINT: + case ES_FIELD_TYPES.GEO_SHAPE: + schema = 'json'; + break; + case ES_FIELD_TYPES.BOOLEAN: + schema = 'boolean'; + break; + case ES_FIELD_TYPES.DATE: + case ES_FIELD_TYPES.DATE_NANOS: + schema = 'datetime'; + break; + case ES_FIELD_TYPES.BYTE: + case ES_FIELD_TYPES.DOUBLE: + case ES_FIELD_TYPES.FLOAT: + case ES_FIELD_TYPES.HALF_FLOAT: + case ES_FIELD_TYPES.INTEGER: + case ES_FIELD_TYPES.LONG: + case ES_FIELD_TYPES.SCALED_FLOAT: + case ES_FIELD_TYPES.SHORT: + schema = 'numeric'; + break; + // keep schema undefined for text based columns + case ES_FIELD_TYPES.KEYWORD: + case ES_FIELD_TYPES.TEXT: + break; + } + + return { id, schema }; + }); + + const dataGrid = useDataGrid(columns); + + const { + pagination, + resetPagination, + setErrorMessage, + setNoDataMessage, + setRowCount, + setStatus, + setTableItems, + sortingColumns, + tableItems, + } = dataGrid; + + const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr); + + const getPreviewData = async () => { + if (aggsArr.length === 0 || groupByArr.length === 0) { + setTableItems([]); + setRowCount(0); + setNoDataMessage( + i18n.translate('xpack.transform.pivotPreview.PivotPreviewIncompleteConfigCalloutBody', { + defaultMessage: 'Please choose at least one group-by field and aggregation.', + }) + ); + return; + } + + setErrorMessage(''); + setNoDataMessage(''); + setStatus(INDEX_STATUS.LOADING); + + try { + const resp = await api.getTransformsPreview(previewRequest); + setTableItems(resp.preview); + setRowCount(resp.preview.length); + setPreviewMappings(resp.generated_dest_index.mappings); + setStatus(INDEX_STATUS.LOADED); + + if (resp.preview.length === 0) { + setNoDataMessage( + i18n.translate('xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', { + defaultMessage: + 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', + }) + ); + } + } catch (e) { + setErrorMessage(getErrorMessage(e)); + setTableItems([]); + setRowCount(0); + setPreviewMappings({ properties: {} }); + setStatus(INDEX_STATUS.ERROR); + } + }; + + useEffect(() => { + resetPagination(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(query)]); + + useEffect(() => { + getPreviewData(); + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [ + indexPatternTitle, + JSON.stringify(aggsArr), + JSON.stringify(groupByArr), + JSON.stringify(query), + /* eslint-enable react-hooks/exhaustive-deps */ + ]); + + if (sortingColumns.length > 0) { + tableItems.sort(multiColumnSortFactory(sortingColumns)); + } + + const pageData = tableItems.slice( + pagination.pageIndex * pagination.pageSize, + (pagination.pageIndex + 1) * pagination.pageSize + ); + + const renderCellValue: RenderCellValue = useMemo(() => { + return ({ + rowIndex, + columnId, + setCellProps, + }: { + rowIndex: number; + columnId: string; + setCellProps: any; + }) => { + const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; + + const cellValue = pageData.hasOwnProperty(adjustedRowIndex) + ? getNestedProperty(pageData[adjustedRowIndex], columnId, null) + : null; + + if (typeof cellValue === 'object' && cellValue !== null) { + return JSON.stringify(cellValue); + } + + if (cellValue === undefined || cellValue === null) { + return null; + } + + if ( + [ES_FIELD_TYPES.DATE, ES_FIELD_TYPES.DATE_NANOS].includes( + previewMappings.properties[columnId].type + ) + ) { + return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); + } + + if (previewMappings.properties[columnId].type === ES_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + + return cellValue; + }; + }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappings.properties]); + + return { + ...dataGrid, + columns, + renderCellValue, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/expanded_row.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/expanded_row.test.tsx.snap deleted file mode 100644 index b668c7d8e4a69..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/expanded_row.test.tsx.snap +++ /dev/null @@ -1,71 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Test against strings, objects and arrays. 1`] = ` - - - - name - : - - - - the-name -    - - - - - nested.inner1 - : - - - - the-inner-1 -    - - - - - nested.inner2 - : - - - - the-inner-2 -    - - - - - arrayString - : - - - - ["the-array-string-1","the-array-string-2"] -    - - - - - arrayObject - : - - - - [{"object1":"the-object-1"},{"object2":"the-objects-2"}] -    - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.test.ts deleted file mode 100644 index d3bf81bba2e56..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.test.ts +++ /dev/null @@ -1,35 +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 { SimpleQuery } from '../../../../common'; - -import { getSourceIndexDevConsoleStatement } from './common'; - -describe('Transform: Source Index Preview Common', () => { - test('getSourceIndexDevConsoleStatement()', () => { - const query: SimpleQuery = { - query_string: { - query: '*', - default_operator: 'AND', - }, - }; - const sourceIndexPreviewDevConsoleStatement = getSourceIndexDevConsoleStatement( - query, - 'the-index-pattern-title' - ); - - expect(sourceIndexPreviewDevConsoleStatement).toBe(`GET the-index-pattern-title/_search -{ - "query": { - "query_string": { - "query": "*", - "default_operator": "AND" - } - } -} -`); - }); -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.ts deleted file mode 100644 index c34675463bf8b..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/common.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PivotQuery } from '../../../../common'; - -export const getSourceIndexDevConsoleStatement = (query: PivotQuery, indexPatternTitle: string) => { - return `GET ${indexPatternTitle}/_search\n${JSON.stringify( - { - query, - }, - null, - 2 - )}\n`; -}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx deleted file mode 100644 index ddd1a1482fd35..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { getNestedProperty } from '../../../../../../common/utils/object_utils'; -import { getFlattenedFields } from '../../../../common'; - -import { ExpandedRow } from './expanded_row'; - -describe('Transform: ', () => { - test('Test against strings, objects and arrays.', () => { - const source = { - name: 'the-name', - nested: { - inner1: 'the-inner-1', - inner2: 'the-inner-2', - }, - arrayString: ['the-array-string-1', 'the-array-string-2'], - arrayObject: [{ object1: 'the-object-1' }, { object2: 'the-objects-2' }], - } as Record; - - const flattenedSource = getFlattenedFields(source).reduce((p, c) => { - p[c] = getNestedProperty(source, c); - if (p[c] === undefined) { - p[c] = source[`"${c}"`]; - } - return p; - }, {} as Record); - - const props = { - item: { - _id: 'the-id', - _source: flattenedSource, - }, - }; - - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.tsx deleted file mode 100644 index 9b83a3e5da8a8..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import { EuiBadge, EuiText } from '@elastic/eui'; - -import { EsDoc } from '../../../../common'; - -export const ExpandedRow: React.FC<{ item: EsDoc }> = ({ item }) => ( - - {Object.entries(item._source).map(([k, value]) => ( - - {k}: - {typeof value === 'string' ? value : JSON.stringify(value)}   - - ))} - -); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx deleted file mode 100644 index 32f6ff9490a0f..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render, wait } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; - -import { getPivotQuery } from '../../../../common'; -import { SearchItems } from '../../../../hooks/use_search_items'; - -import { SourceIndexPreview } from './source_index_preview'; - -jest.mock('../../../../../shared_imports'); -jest.mock('../../../../../app/app_dependencies'); - -describe('Transform: ', () => { - // Using the async/await wait()/done() pattern to avoid act() errors. - test('Minimal initialization', async done => { - // Arrange - const props = { - indexPattern: { - title: 'the-index-pattern-title', - fields: [] as any[], - } as SearchItems['indexPattern'], - query: getPivotQuery('the-query'), - }; - const { getByText } = render(); - - // Act - // Assert - expect(getByText(`Source index ${props.indexPattern.title}`)).toBeInTheDocument(); - await wait(); - done(); - }); -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx deleted file mode 100644 index bcdeb7ddb0d36..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx +++ /dev/null @@ -1,293 +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 moment from 'moment-timezone'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; - -import { i18n } from '@kbn/i18n'; - -import { - EuiButtonIcon, - EuiCallOut, - EuiCodeBlock, - EuiCopy, - EuiDataGrid, - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; - -import { KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/common'; - -import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils'; -import { getNestedProperty } from '../../../../../../common/utils/object_utils'; - -import { - euiDataGridStyle, - euiDataGridToolbarSettings, - EsFieldName, - PivotQuery, - INIT_MAX_COLUMNS, -} from '../../../../common'; -import { SearchItems } from '../../../../hooks/use_search_items'; -import { useToastNotifications } from '../../../../app_dependencies'; - -import { getSourceIndexDevConsoleStatement } from './common'; -import { SOURCE_INDEX_STATUS, useSourceIndexData } from './use_source_index_data'; - -interface SourceIndexPreviewTitle { - indexPatternTitle: string; -} -const SourceIndexPreviewTitle: React.FC = ({ indexPatternTitle }) => ( - - - {i18n.translate('xpack.transform.sourceIndexPreview.sourceIndexPatternTitle', { - defaultMessage: 'Source index {indexPatternTitle}', - values: { indexPatternTitle }, - })} - - -); - -interface Props { - indexPattern: SearchItems['indexPattern']; - query: PivotQuery; -} - -export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, query }) => { - const toastNotifications = useToastNotifications(); - const allFields = indexPattern.fields.map(f => f.name); - const indexPatternFields: string[] = allFields.filter(f => { - if (indexPattern.metaFields.includes(f)) { - return false; - } - - const fieldParts = f.split('.'); - const lastPart = fieldParts.pop(); - if (lastPart === 'keyword' && allFields.includes(fieldParts.join('.'))) { - return false; - } - - return true; - }); - - // Column visibility - const [visibleColumns, setVisibleColumns] = useState([]); - - useEffect(() => { - setVisibleColumns(indexPatternFields.splice(0, INIT_MAX_COLUMNS)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indexPatternFields.join()]); - - const { - errorMessage, - pagination, - setPagination, - setSortingColumns, - rowCount, - sortingColumns, - status, - tableItems: data, - } = useSourceIndexData(indexPattern, query); - - // EuiDataGrid State - const dataGridColumns = [ - ...indexPatternFields.map(id => { - const field = indexPattern.fields.getByName(id); - - // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] - // To fall back to the default string schema it needs to be undefined. - let schema; - - switch (field?.type) { - case KBN_FIELD_TYPES.BOOLEAN: - schema = 'boolean'; - break; - case KBN_FIELD_TYPES.DATE: - schema = 'datetime'; - break; - case KBN_FIELD_TYPES.GEO_POINT: - case KBN_FIELD_TYPES.GEO_SHAPE: - schema = 'json'; - break; - case KBN_FIELD_TYPES.NUMBER: - schema = 'numeric'; - break; - } - - return { id, schema }; - }), - ]; - - const onSort = useCallback( - (sc: Array<{ id: string; direction: 'asc' | 'desc' }>) => { - // Check if an unsupported column type for sorting was selected. - const invalidSortingColumnns = sc.reduce((arr, current) => { - const columnType = dataGridColumns.find(dgc => dgc.id === current.id); - if (columnType?.schema === 'json') { - arr.push(current.id); - } - return arr; - }, []); - if (invalidSortingColumnns.length === 0) { - setSortingColumns(sc); - } else { - invalidSortingColumnns.forEach(columnId => { - toastNotifications.addDanger( - i18n.translate('xpack.transform.sourceIndexPreview.invalidSortingColumnError', { - defaultMessage: `The column '{columnId}' cannot be used for sorting.`, - values: { columnId }, - }) - ); - }); - } - }, - [dataGridColumns, setSortingColumns, toastNotifications] - ); - - const onChangeItemsPerPage = useCallback( - pageSize => { - setPagination(p => { - const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); - return { pageIndex, pageSize }; - }); - }, - [setPagination] - ); - - const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ - setPagination, - ]); - - const renderCellValue = useMemo(() => { - return ({ - rowIndex, - columnId, - setCellProps, - }: { - rowIndex: number; - columnId: string; - setCellProps: any; - }) => { - const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; - - const cellValue = data.hasOwnProperty(adjustedRowIndex) - ? getNestedProperty(data[adjustedRowIndex], columnId, null) - : null; - - if (typeof cellValue === 'object' && cellValue !== null) { - return JSON.stringify(cellValue); - } - - if (cellValue === undefined || cellValue === null) { - return null; - } - - const field = indexPattern.fields.getByName(columnId); - if (field?.type === KBN_FIELD_TYPES.DATE) { - return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); - } - - if (field?.type === KBN_FIELD_TYPES.BOOLEAN) { - return cellValue ? 'true' : 'false'; - } - - return cellValue; - }; - }, [data, indexPattern.fields, pagination.pageIndex, pagination.pageSize]); - - if (status === SOURCE_INDEX_STATUS.LOADED && data.length === 0) { - return ( -
- - -

- {i18n.translate('xpack.transform.sourceIndexPreview.SourceIndexNoDataCalloutBody', { - defaultMessage: - 'The query for the source index returned no results. Please make sure you have sufficient permissions, the index contains documents and your query is not too restrictive.', - })} -

-
-
- ); - } - - const euiCopyText = i18n.translate('xpack.transform.sourceIndexPreview.copyClipboardTooltip', { - defaultMessage: 'Copy Dev Console statement of the source index preview to the clipboard.', - }); - - return ( -
- - - - - - - {(copy: () => void) => ( - - )} - - - -
- {status === SOURCE_INDEX_STATUS.LOADING && } - {status !== SOURCE_INDEX_STATUS.LOADING && ( - - )} -
- {status === SOURCE_INDEX_STATUS.ERROR && ( -
- - - {errorMessage} - - - -
- )} - -
- ); -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx deleted file mode 100644 index 5a1d8a8db5b42..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx +++ /dev/null @@ -1,43 +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 { renderHook } from '@testing-library/react-hooks'; -import '@testing-library/jest-dom/extend-expect'; - -import { SimpleQuery } from '../../../../common'; -import { - SOURCE_INDEX_STATUS, - useSourceIndexData, - UseSourceIndexDataReturnType, -} from './use_source_index_data'; - -jest.mock('../../../../hooks/use_api'); - -const query: SimpleQuery = { - query_string: { - query: '*', - default_operator: 'AND', - }, -}; - -describe('useSourceIndexData', () => { - test('indexPattern set triggers loading', async done => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceIndexData({ id: 'the-id', title: 'the-title', fields: [] }, query) - ); - const sourceIndexObj: UseSourceIndexDataReturnType = result.current; - - await waitForNextUpdate(); - - expect(sourceIndexObj.errorMessage).toBe(''); - expect(sourceIndexObj.status).toBe(SOURCE_INDEX_STATUS.LOADING); - expect(sourceIndexObj.tableItems).toEqual([]); - done(); - }); - - // TODO add more tests to check data retrieved via `api.esSearch()`. - // This needs more investigation in regards to jest's React Hooks support. -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts deleted file mode 100644 index 5301a3c168a51..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts +++ /dev/null @@ -1,143 +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 { useEffect, useState, Dispatch, SetStateAction } from 'react'; - -import { SearchResponse } from 'elasticsearch'; - -import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui'; - -import { IIndexPattern } from 'src/plugins/data/public'; - -import { Dictionary } from '../../../../../../common/types/common'; - -import { isDefaultQuery, matchAllQuery, EsDocSource, PivotQuery } from '../../../../common'; -import { useApi } from '../../../../hooks/use_api'; - -export enum SOURCE_INDEX_STATUS { - UNUSED, - LOADING, - LOADED, - ERROR, -} - -type EsSorting = Dictionary<{ - order: 'asc' | 'desc'; -}>; - -interface ErrorResponse { - request: Dictionary; - response: Dictionary; - body: { - statusCode: number; - error: string; - message: string; - }; - name: string; - req: Dictionary; - res: Dictionary; -} - -const isErrorResponse = (arg: any): arg is ErrorResponse => { - return arg?.body?.error !== undefined && arg?.body?.message !== undefined; -}; - -// The types specified in `@types/elasticsearch` are out of date and still have `total: number`. -interface SearchResponse7 extends SearchResponse { - hits: SearchResponse['hits'] & { - total: { - value: number; - relation: string; - }; - }; -} - -type SourceIndexSearchResponse = SearchResponse7; - -type SourceIndexPagination = Pick; -const defaultPagination: SourceIndexPagination = { pageIndex: 0, pageSize: 5 }; - -export interface UseSourceIndexDataReturnType { - errorMessage: string; - pagination: SourceIndexPagination; - setPagination: Dispatch>; - setSortingColumns: Dispatch>; - rowCount: number; - sortingColumns: EuiDataGridSorting['columns']; - status: SOURCE_INDEX_STATUS; - tableItems: EsDocSource[]; -} - -export const useSourceIndexData = ( - indexPattern: IIndexPattern, - query: PivotQuery -): UseSourceIndexDataReturnType => { - const [errorMessage, setErrorMessage] = useState(''); - const [status, setStatus] = useState(SOURCE_INDEX_STATUS.UNUSED); - const [pagination, setPagination] = useState(defaultPagination); - const [sortingColumns, setSortingColumns] = useState([]); - const [rowCount, setRowCount] = useState(0); - const [tableItems, setTableItems] = useState([]); - const api = useApi(); - - useEffect(() => { - setPagination(defaultPagination); - }, [query]); - - const getSourceIndexData = async function() { - setErrorMessage(''); - setStatus(SOURCE_INDEX_STATUS.LOADING); - - const sort: EsSorting = sortingColumns.reduce((s, column) => { - s[column.id] = { order: column.direction }; - return s; - }, {} as EsSorting); - - const esSearchRequest = { - index: indexPattern.title, - body: { - // Instead of using the default query (`*`), fall back to a more efficient `match_all` query. - query: isDefaultQuery(query) ? matchAllQuery : query, - from: pagination.pageIndex * pagination.pageSize, - size: pagination.pageSize, - ...(Object.keys(sort).length > 0 ? { sort } : {}), - }, - }; - - try { - const resp: SourceIndexSearchResponse = await api.esSearch(esSearchRequest); - - const docs = resp.hits.hits.map(d => d._source); - - setRowCount(resp.hits.total.value); - setTableItems(docs); - setStatus(SOURCE_INDEX_STATUS.LOADED); - } catch (e) { - if (isErrorResponse(e)) { - setErrorMessage(`${e.body.error}: ${e.body.message}`); - } else { - setErrorMessage(JSON.stringify(e, null, 2)); - } - setStatus(SOURCE_INDEX_STATUS.ERROR); - } - }; - - useEffect(() => { - getSourceIndexData(); - // custom comparison - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); - return { - errorMessage, - pagination, - setPagination, - setSortingColumns, - rowCount, - sortingColumns, - status, - tableItems, - }; -}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 320e405b5d437..0e6e2c1a38d0e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -35,19 +35,19 @@ import { import { useXJsonMode } from '../../../../../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; -import { PivotPreview } from '../../../../components/pivot_preview'; +import { DataGrid } from '../../../../../shared_imports'; + +import { + getIndexDevConsoleStatement, + getPivotPreviewDevConsoleStatement, +} from '../../../../common/data_grid'; import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items'; +import { useIndexData } from '../../../../hooks/use_index_data'; +import { usePivotData } from '../../../../hooks/use_pivot_data'; import { useToastNotifications } from '../../../../app_dependencies'; -import { TransformPivotConfig } from '../../../../common'; import { dictionaryToArray, Dictionary } from '../../../../../../common/types/common'; -import { DropDown } from '../aggregation_dropdown'; -import { AggListForm } from '../aggregation_list'; -import { GroupByListForm } from '../group_by_list'; -import { SourceIndexPreview } from '../source_index_preview'; -import { SwitchModal } from './switch_modal'; - import { getPivotQuery, getPreviewRequestBody, @@ -61,11 +61,17 @@ import { PivotGroupByConfig, PivotGroupByConfigDict, PivotSupportedGroupByAggs, + TransformPivotConfig, PIVOT_SUPPORTED_AGGS, PIVOT_SUPPORTED_GROUP_BY_AGGS, } from '../../../../common'; +import { DropDown } from '../aggregation_dropdown'; +import { AggListForm } from '../aggregation_list'; +import { GroupByListForm } from '../group_by_list'; + import { getPivotDropdownOptions } from './common'; +import { SwitchModal } from './switch_modal'; export interface StepDefineExposedState { aggList: PivotAggsConfigDict; @@ -296,7 +302,6 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, return; } } catch (e) { - console.log('Invalid syntax', JSON.stringify(e, null, 2)); // eslint-disable-line no-console setErrorMessage({ query: query.query as string, message: e.message }); } }; @@ -593,6 +598,9 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, /* eslint-enable react-hooks/exhaustive-deps */ ]); + const indexPreviewProps = useIndexData(indexPattern, pivotQuery); + const pivotPreviewProps = usePivotData(indexPattern.title, pivotQuery, aggList, groupByList); + // TODO This should use the actual value of `indices.query.bool.max_clause_count` const maxIndexFields = 1024; const numIndexFields = indexPattern.fields.length; @@ -973,13 +981,37 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, - + - diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index f31514e67003b..b9021f4ee5b11 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -17,8 +17,19 @@ import { EuiText, } from '@elastic/eui'; -import { getPivotQuery, isDefaultQuery, isMatchAllQuery } from '../../../../common'; -import { PivotPreview } from '../../../../components/pivot_preview'; +import { dictionaryToArray } from '../../../../../../common/types/common'; + +import { DataGrid } from '../../../../../shared_imports'; + +import { useToastNotifications } from '../../../../app_dependencies'; +import { + getPivotQuery, + getPivotPreviewDevConsoleStatement, + getPreviewRequestBody, + isDefaultQuery, + isMatchAllQuery, +} from '../../../../common'; +import { usePivotData } from '../../../../hooks/use_pivot_data'; import { SearchItems } from '../../../../hooks/use_search_items'; import { AggListSummary } from '../aggregation_list'; @@ -35,8 +46,25 @@ export const StepDefineSummary: FC = ({ formState: { searchString, searchQuery, groupByList, aggList }, searchItems, }) => { + const toastNotifications = useToastNotifications(); + const pivotAggsArr = dictionaryToArray(aggList); + const pivotGroupByArr = dictionaryToArray(groupByList); const pivotQuery = getPivotQuery(searchQuery); + const previewRequest = getPreviewRequestBody( + searchItems.indexPattern.title, + pivotQuery, + pivotGroupByArr, + pivotAggsArr + ); + + const pivotPreviewProps = usePivotData( + searchItems.indexPattern.title, + pivotQuery, + aggList, + groupByList + ); + return ( @@ -117,11 +145,20 @@ export const StepDefineSummary: FC = ({ - diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx index eaaedc2eb77ce..e183712b390cf 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -6,37 +6,39 @@ import React, { FC } from 'react'; -import { SearchItems } from '../../../../hooks/use_search_items'; +import { DataGrid } from '../../../../../shared_imports'; +import { useToastNotifications } from '../../../../app_dependencies'; import { getPivotQuery, TransformPivotConfig } from '../../../../common'; +import { usePivotData } from '../../../../hooks/use_pivot_data'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { applyTransformConfigToDefineState, getDefaultStepDefineState, } from '../../../create_transform/components/step_define/'; -import { PivotPreview } from '../../../../components/pivot_preview'; -interface Props { +interface ExpandedRowPreviewPaneProps { transformConfig: TransformPivotConfig; } -export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { - const previewConfig = applyTransformConfigToDefineState( +export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { + const toastNotifications = useToastNotifications(); + const { aggList, groupByList, searchQuery } = applyTransformConfigToDefineState( getDefaultStepDefineState({} as SearchItems), transformConfig ); - + const pivotQuery = getPivotQuery(searchQuery); const indexPatternTitle = Array.isArray(transformConfig.source.index) ? transformConfig.source.index.join(',') : transformConfig.source.index; + const pivotPreviewProps = usePivotData(indexPatternTitle, pivotQuery, aggList, groupByList); return ( - ); }; diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index 494b6db6aafe0..bcd8e53e3d191 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -17,3 +17,18 @@ export { } from '../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; export { getErrorMessage } from '../../ml/common/util/errors'; + +export { + getDataGridSchemaFromKibanaFieldType, + getFieldsFromKibanaIndexPattern, + multiColumnSortFactory, + useDataGrid, + useRenderCellValue, + DataGrid, + EsSorting, + RenderCellValue, + SearchResponse7, + UseDataGridReturnType, + UseIndexDataReturnType, +} from '../../ml/public/application/components/data_grid'; +export { INDEX_STATUS } from '../../ml/public/application/data_frame_analytics/common'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index deb3053d28658..87e8e2e7a41e2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9443,14 +9443,8 @@ "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixLabel": "分類混同行列", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixPredictedLabel": "予測されたラベル", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTooltip": "マルチクラス混同行列には、分析が実際のクラスで正しくデータポイントを分類した発生数と、別のクラスで誤分類した発生数が含まれます。", - "xpack.ml.dataframe.analytics.classificationExploration.documentsShownHelpText": "予測があるドキュメントを示す", "xpack.ml.dataframe.analytics.classificationExploration.evaluateJobIdTitle": "分類ジョブID {jobId}の評価", - "xpack.ml.dataframe.analytics.classificationExploration.firstDocumentsShownHelpText": "予測がある最初の{searchSize}のドキュメントを示す", "xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount": "{docsCount, plural, one {# doc} other {# docs}}が評価されました", - "xpack.ml.dataframe.analytics.classificationExploration.jobCapsFetchError": "結果を取得できません。インデックスのフィールドデータの読み込み中にエラーが発生しました。", - "xpack.ml.dataframe.analytics.classificationExploration.jobConfigurationFetchError": "結果を取得できません。ジョブ構成データの読み込み中にエラーが発生しました。", - "xpack.ml.dataframe.analytics.classificationExploration.jobConfigurationNoResultsMessage": "結果が見つかりませんでした。", - "xpack.ml.dataframe.analytics.classificationExploration.regressionDocsLink": "回帰評価ドキュメント ", "xpack.ml.dataframe.analytics.classificationExploration.showActions": "アクションを表示", "xpack.ml.dataframe.analytics.classificationExploration.showAllColumns": "すべての列を表示", "xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle": "分類ジョブID {jobId}のデスティネーションインデックス", @@ -9525,31 +9519,17 @@ "xpack.ml.dataframe.analytics.create.startDataFrameAnalyticsSuccessMessage": "データフレーム分析 {jobId} の開始リクエストが受け付けられました。", "xpack.ml.dataframe.analytics.create.trainingPercentLabel": "トレーニングパーセンテージ", "xpack.ml.dataframe.analytics.exploration.colorRangeLegendTitle": "機能影響スコア", - "xpack.ml.dataframe.analytics.exploration.dataGridAriaLabel": "外れ値検出結果表", "xpack.ml.dataframe.analytics.exploration.experimentalBadgeLabel": "実験的", "xpack.ml.dataframe.analytics.exploration.experimentalBadgeTooltipContent": "データフレーム分析は実験段階の機能です。フィードバックをお待ちしています。", "xpack.ml.dataframe.analytics.exploration.indexError": "インデックスデータの読み込み中にエラーが発生しました。", "xpack.ml.dataframe.analytics.exploration.jobIdTitle": "外れ値検出ジョブID {jobId}", - "xpack.ml.dataframe.analytics.exploration.noDataCalloutBody": "インデックスのクエリが結果を返しませんでした。インデックスにドキュメントが含まれていて、クエリ要件が妥当であることを確認してください。", "xpack.ml.dataframe.analytics.exploration.title": "分析の探索", - "xpack.ml.dataframe.analytics.regressionExploration.documentsShownHelpText": "予測があるドキュメントを示す", - "xpack.ml.dataframe.analytics.regressionExploration.evaluateError": "データの読み込み中にエラーが発生しました。", "xpack.ml.dataframe.analytics.regressionExploration.evaluateJobIdTitle": "回帰ジョブID {jobId}の評価", - "xpack.ml.dataframe.analytics.regressionExploration.fieldSelection": "{docFieldsCount, number} 件中 showing {selectedFieldsLength, number} 件の{docFieldsCount, plural, one {フィールド} other {フィールド}}", - "xpack.ml.dataframe.analytics.regressionExploration.firstDocumentsShownHelpText": "予測がある最初の{searchSize}のドキュメントを示す", - "xpack.ml.dataframe.analytics.regressionExploration.generalError": "データの読み込み中にエラーが発生しました。", "xpack.ml.dataframe.analytics.regressionExploration.generalizationDocsCount": "{docsCount, plural, one {# doc} other {# docs}}が評価されました", "xpack.ml.dataframe.analytics.regressionExploration.generalizationErrorTitle": "一般化エラー", "xpack.ml.dataframe.analytics.regressionExploration.indexError": "インデックスデータの読み込み中にエラーが発生しました。", - "xpack.ml.dataframe.analytics.regressionExploration.jobCapsFetchError": "結果を取得できません。インデックスのフィールドデータの読み込み中にエラーが発生しました。", - "xpack.ml.dataframe.analytics.regressionExploration.jobConfigurationFetchError": "結果を取得できません。ジョブ構成データの読み込み中にエラーが発生しました。", "xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorText": "平均二乗エラー", "xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorTooltipContent": "回帰分析モデルの実行の効果を測定します。真値と予測値の間の差異の二乗平均合計。", - "xpack.ml.dataframe.analytics.regressionExploration.noDataCalloutBody": "インデックスのクエリが結果を返しませんでした。ジョブが完了済みで、インデックスにドキュメントがあることを確認してください。", - "xpack.ml.dataframe.analytics.regressionExploration.noDataCalloutTitle": "空のインデックスクエリ結果。", - "xpack.ml.dataframe.analytics.regressionExploration.noIndexCalloutBody": "インデックスのクエリが結果を返しませんでした。デスティネーションインデックスが存在し、ドキュメントがあることを確認してください。", - "xpack.ml.dataframe.analytics.regressionExploration.queryParsingErrorBody": "クエリ構文が無効であり、結果を返しませんでした。クエリ構文を確認し、再試行してください。", - "xpack.ml.dataframe.analytics.regressionExploration.queryParsingErrorMessage": "クエリをパースできません。", "xpack.ml.dataframe.analytics.regressionExploration.rSquaredText": "R の二乗", "xpack.ml.dataframe.analytics.regressionExploration.rSquaredTooltipContent": "適合度を表します。モデルによる観察された結果の複製の効果を測定します。", "xpack.ml.dataframe.analytics.regressionExploration.tableJobIdTitle": "回帰ジョブID {jobId}のデスティネーションインデックス", @@ -15567,19 +15547,11 @@ "xpack.transform.newTransform.searchSelection.savedObjectType.indexPattern": "インデックスパターン", "xpack.transform.newTransform.searchSelection.savedObjectType.search": "保存検索", "xpack.transform.pivotPreview.copyClipboardTooltip": "ピボットプレビューの開発コンソールステートメントをクリップボードにコピーします。", - "xpack.transform.pivotPreview.PivotPreviewError": "ピボットプレビューの読み込み中にエラーが発生しました。", "xpack.transform.pivotPreview.PivotPreviewIncompleteConfigCalloutBody": "group-by フィールドと集約を 1 つ以上選んでください。", "xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody": "プレビューリクエストはデータを返しませんでした。オプションのクエリがデータを返し、グループ分け基準により使用されるフィールドと集約フィールドに値が存在することを確認してください。", - "xpack.transform.pivotPreview.PivotPreviewNoDataCalloutTitle": "ピボットプレビューを利用できません", "xpack.transform.pivotPreview.PivotPreviewTitle": "ピボットプレビューを変換", "xpack.transform.progress": "進捗", "xpack.transform.sourceIndex": "ソースインデックス", - "xpack.transform.sourceIndexPreview.copyClipboardTooltip": "ソースインデックスプレビューの開発コンソールステートメントをクリップボードにコピーします。", - "xpack.transform.sourceIndexPreview.invalidSortingColumnError": "列「{columnId}」は並べ替えに使用できません。", - "xpack.transform.sourceIndexPreview.SourceIndexNoDataCalloutBody": "ソースインデックスのクエリが結果を返しませんでした。インデックスにドキュメントが含まれていて、クエリ要件が妥当であることを確認してください。", - "xpack.transform.sourceIndexPreview.SourceIndexNoDataCalloutTitle": "ソースインデックスクエリの結果がありません", - "xpack.transform.sourceIndexPreview.sourceIndexPatternError": "ソースインデックスデータの読み込み中にエラーが発生しました。", - "xpack.transform.sourceIndexPreview.sourceIndexPatternTitle": "ソースインデックス {indexPatternTitle}", "xpack.transform.statsBar.batchTransformsLabel": "一斉", "xpack.transform.statsBar.continuousTransformsLabel": "連続", "xpack.transform.statsBar.failedTransformsLabel": "失敗", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3b757f169828c..4a5ed3291a915 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9446,14 +9446,8 @@ "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixLabel": "分类混淆矩阵", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixPredictedLabel": "预测标签", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTooltip": "多类混淆矩阵包含分析使用数据点的实际类正确分类数据点的次数以及分析使用其他类错误分类这些数据点的次数", - "xpack.ml.dataframe.analytics.classificationExploration.documentsShownHelpText": "正在显示有相关预测存在的文档", "xpack.ml.dataframe.analytics.classificationExploration.evaluateJobIdTitle": "分类作业 ID {jobId} 的评估", - "xpack.ml.dataframe.analytics.classificationExploration.firstDocumentsShownHelpText": "正在显示有相关预测存在的前 {searchSize} 个文档", "xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount": "{docsCount, plural, one {# 个文档} other {# 个文档}}已评估", - "xpack.ml.dataframe.analytics.classificationExploration.jobCapsFetchError": "无法提取结果。加载索引的字段数据时发生错误。", - "xpack.ml.dataframe.analytics.classificationExploration.jobConfigurationFetchError": "无法提取结果。加载作业配置数据时发生错误。", - "xpack.ml.dataframe.analytics.classificationExploration.jobConfigurationNoResultsMessage": "未找到结果。", - "xpack.ml.dataframe.analytics.classificationExploration.regressionDocsLink": "回归评估文档 ", "xpack.ml.dataframe.analytics.classificationExploration.showActions": "显示操作", "xpack.ml.dataframe.analytics.classificationExploration.showAllColumns": "显示所有列", "xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle": "分类作业 ID {jobId} 的目标索引", @@ -9528,31 +9522,17 @@ "xpack.ml.dataframe.analytics.create.startDataFrameAnalyticsSuccessMessage": "数据帧分析 {jobId} 启动请求已确认。", "xpack.ml.dataframe.analytics.create.trainingPercentLabel": "训练百分比", "xpack.ml.dataframe.analytics.exploration.colorRangeLegendTitle": "功能影响分数", - "xpack.ml.dataframe.analytics.exploration.dataGridAriaLabel": "离群值检测结果表", "xpack.ml.dataframe.analytics.exploration.experimentalBadgeLabel": "实验性", "xpack.ml.dataframe.analytics.exploration.experimentalBadgeTooltipContent": "数据帧分析为实验功能。我们很乐意听取您的反馈意见。", "xpack.ml.dataframe.analytics.exploration.indexError": "加载索引数据时出错。", "xpack.ml.dataframe.analytics.exploration.jobIdTitle": "离群值检测作业 ID {jobId}", - "xpack.ml.dataframe.analytics.exploration.noDataCalloutBody": "该索引的查询未返回结果。请确保索引包含文档且您的查询限制不过于严格。", "xpack.ml.dataframe.analytics.exploration.title": "分析浏览", - "xpack.ml.dataframe.analytics.regressionExploration.documentsShownHelpText": "正在显示有相关预测存在的文档", - "xpack.ml.dataframe.analytics.regressionExploration.evaluateError": "加载数据时出错。", "xpack.ml.dataframe.analytics.regressionExploration.evaluateJobIdTitle": "回归作业 ID {jobId} 的评估", - "xpack.ml.dataframe.analytics.regressionExploration.fieldSelection": "已选择 {docFieldsCount, number} 个{docFieldsCount, plural, one {字段} other {字段}}中的 {selectedFieldsLength, number} 个", - "xpack.ml.dataframe.analytics.regressionExploration.firstDocumentsShownHelpText": "正在显示有相关预测存在的前 {searchSize} 个文档", - "xpack.ml.dataframe.analytics.regressionExploration.generalError": "加载数据时出错。", "xpack.ml.dataframe.analytics.regressionExploration.generalizationDocsCount": "{docsCount, plural, one {# 个文档} other {# 个文档}}已评估", "xpack.ml.dataframe.analytics.regressionExploration.generalizationErrorTitle": "泛化误差", "xpack.ml.dataframe.analytics.regressionExploration.indexError": "加载索引数据时出错。", - "xpack.ml.dataframe.analytics.regressionExploration.jobCapsFetchError": "无法提取结果。加载索引的字段数据时发生错误。", - "xpack.ml.dataframe.analytics.regressionExploration.jobConfigurationFetchError": "无法提取结果。加载作业配置数据时发生错误。", "xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorText": "均方误差", "xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorTooltipContent": "度量回归分析模型的表现。真实值与预测值之差的平均平方和。", - "xpack.ml.dataframe.analytics.regressionExploration.noDataCalloutBody": "该索引的查询未返回结果。请确保作业已完成且索引包含文档。", - "xpack.ml.dataframe.analytics.regressionExploration.noDataCalloutTitle": "空的索引查询结果。", - "xpack.ml.dataframe.analytics.regressionExploration.noIndexCalloutBody": "该索引的查询未返回结果。请确保目标索引存在且包含文档。", - "xpack.ml.dataframe.analytics.regressionExploration.queryParsingErrorBody": "查询语法无效,未返回任何结果。请检查查询语法并重试。", - "xpack.ml.dataframe.analytics.regressionExploration.queryParsingErrorMessage": "无法解析查询。", "xpack.ml.dataframe.analytics.regressionExploration.rSquaredText": "R 平方", "xpack.ml.dataframe.analytics.regressionExploration.rSquaredTooltipContent": "表示拟合优度。度量模型复制被观察结果的优良性。", "xpack.ml.dataframe.analytics.regressionExploration.tableJobIdTitle": "回归作业 ID {jobId} 的目标索引", @@ -15571,19 +15551,11 @@ "xpack.transform.newTransform.searchSelection.savedObjectType.indexPattern": "索引模式", "xpack.transform.newTransform.searchSelection.savedObjectType.search": "已保存搜索", "xpack.transform.pivotPreview.copyClipboardTooltip": "将透视预览的开发控制台语句复制到剪贴板。", - "xpack.transform.pivotPreview.PivotPreviewError": "加载数据透视表预览时出错。", "xpack.transform.pivotPreview.PivotPreviewIncompleteConfigCalloutBody": "请至少选择一个分组依据字段和聚合。", "xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody": "预览请求未返回任何数据。请确保可选查询返回数据且存在分组依据和聚合字段使用的字段的值。", - "xpack.transform.pivotPreview.PivotPreviewNoDataCalloutTitle": "数据透视表预览不可用", "xpack.transform.pivotPreview.PivotPreviewTitle": "转换数据透视表预览", "xpack.transform.progress": "进度", "xpack.transform.sourceIndex": "源索引", - "xpack.transform.sourceIndexPreview.copyClipboardTooltip": "将源索引预览的开发控制台语句复制到剪贴板。", - "xpack.transform.sourceIndexPreview.invalidSortingColumnError": "列“{columnId}”无法用于排序。", - "xpack.transform.sourceIndexPreview.SourceIndexNoDataCalloutBody": "源索引的查询未返回结果。请确保索引包含文档且您的查询限制不过于严格。", - "xpack.transform.sourceIndexPreview.SourceIndexNoDataCalloutTitle": "源索引查询结果为空。", - "xpack.transform.sourceIndexPreview.sourceIndexPatternError": "加载源索引数据时出错。", - "xpack.transform.sourceIndexPreview.sourceIndexPatternTitle": "源索引 {indexPatternTitle}", "xpack.transform.statsBar.batchTransformsLabel": "批量", "xpack.transform.statsBar.continuousTransformsLabel": "连续", "xpack.transform.statsBar.failedTransformsLabel": "失败", diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index bea6b814ee8a3..dbdbca0368146 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -90,7 +90,7 @@ export default function({ getService }: FtrProviderContext) { mode: 'batch', progress: '100', }, - sourcePreview: { + indexPreview: { columns: 20, rows: 5, }, @@ -144,7 +144,7 @@ export default function({ getService }: FtrProviderContext) { mode: 'batch', progress: '100', }, - sourcePreview: { + indexPreview: { columns: 20, rows: 5, }, @@ -180,14 +180,14 @@ export default function({ getService }: FtrProviderContext) { await transform.wizard.assertDefineStepActive(); }); - it('loads the source index preview', async () => { - await transform.wizard.assertSourceIndexPreviewLoaded(); + it('loads the index preview', async () => { + await transform.wizard.assertIndexPreviewLoaded(); }); - it('shows the source index preview', async () => { - await transform.wizard.assertSourceIndexPreview( - testData.expected.sourcePreview.columns, - testData.expected.sourcePreview.rows + it('shows the index preview', async () => { + await transform.wizard.assertIndexPreview( + testData.expected.indexPreview.columns, + testData.expected.indexPreview.rows ); }); diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts index 993bd3a79abbc..fd5673de0d7a7 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts @@ -65,7 +65,7 @@ export default function({ getService }: FtrProviderContext) { progress: '100', }, sourceIndex: 'ft_farequote', - sourcePreview: { + indexPreview: { column: 2, values: ['ASA'], }, @@ -101,14 +101,14 @@ export default function({ getService }: FtrProviderContext) { await transform.wizard.assertDefineStepActive(); }); - it('loads the source index preview', async () => { - await transform.wizard.assertSourceIndexPreviewLoaded(); + it('loads the index preview', async () => { + await transform.wizard.assertIndexPreviewLoaded(); }); - it('shows the filtered source index preview', async () => { - await transform.wizard.assertSourceIndexPreviewColumnValues( - testData.expected.sourcePreview.column, - testData.expected.sourcePreview.values + it('shows the filtered index preview', async () => { + await transform.wizard.assertIndexPreviewColumnValues( + testData.expected.indexPreview.column, + testData.expected.indexPreview.values ); }); diff --git a/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts b/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts index bada1d42b564a..bd7d76e34b447 100644 --- a/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts +++ b/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts @@ -41,7 +41,7 @@ export function MachineLearningDataFrameAnalyticsProvider( }, async assertRegressionTablePanelExists() { - await testSubjects.existOrFail('mlDFAnalyticsRegressionExplorationTablePanel'); + await testSubjects.existOrFail('mlDFAnalyticsExplorationTablePanel'); }, async assertClassificationEvaluatePanelElementsExists() { @@ -50,7 +50,7 @@ export function MachineLearningDataFrameAnalyticsProvider( }, async assertClassificationTablePanelExists() { - await testSubjects.existOrFail('mlDFAnalyticsClassificationExplorationTablePanel'); + await testSubjects.existOrFail('mlDFAnalyticsExplorationTablePanel'); }, async assertOutlierTablePanelExists() { diff --git a/x-pack/test/functional/services/transform_ui/wizard.ts b/x-pack/test/functional/services/transform_ui/wizard.ts index ba9096a372d9a..e63af493438d6 100644 --- a/x-pack/test/functional/services/transform_ui/wizard.ts +++ b/x-pack/test/functional/services/transform_ui/wizard.ts @@ -52,8 +52,8 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await this.assertDetailsSummaryExists(); }, - async assertSourceIndexPreviewExists(subSelector?: string) { - let selector = 'transformSourceIndexPreview'; + async assertIndexPreviewExists(subSelector?: string) { + let selector = 'transformIndexPreview'; if (subSelector !== undefined) { selector = `${selector} ${subSelector}`; } else { @@ -62,8 +62,8 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail(selector); }, - async assertSourceIndexPreviewLoaded() { - await this.assertSourceIndexPreviewExists('loaded'); + async assertIndexPreviewLoaded() { + await this.assertIndexPreviewExists('loaded'); }, async assertPivotPreviewExists(subSelector?: string) { @@ -124,10 +124,10 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { }); }, - async assertSourceIndexPreview(columns: number, rows: number) { + async assertIndexPreview(columns: number, rows: number) { await retry.tryForTime(2000, async () => { // get a 2D array of rows and cell values - const rowsData = await this.parseEuiDataGrid('transformSourceIndexPreview'); + const rowsData = await this.parseEuiDataGrid('transformIndexPreview'); expect(rowsData).to.length( rows, @@ -143,8 +143,8 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { }); }, - async assertSourceIndexPreviewColumnValues(column: number, values: string[]) { - await this.assertEuiDataGridColumnValues('transformSourceIndexPreview', column, values); + async assertIndexPreviewColumnValues(column: number, values: string[]) { + await this.assertEuiDataGridColumnValues('transformIndexPreview', column, values); }, async assertPivotPreviewColumnValues(column: number, values: string[]) { From 66074f9042be279a3719e397d232c372fe9afc19 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 24 Apr 2020 14:56:59 +0300 Subject: [PATCH 02/13] Index pattern management UI -> TypeScript and New Platform Ready (edit_index_pattern) (#64184) * draft * Converted edit_index_pattern to React. Created 'tab' component. * Some fixes * returned state_container * Fixed tests and translation * Some refactoring * Fixed tests * rermove unused translations * update snapshots * Some refactoring Co-authored-by: Elastic Machine Co-authored-by: Matt Kime --- .../edit_index_pattern.html | 151 +----- .../edit_index_pattern/edit_index_pattern.js | 511 ------------------ .../edit_index_pattern/edit_index_pattern.tsx | 238 ++++++++ .../edit_index_pattern_state_container.ts | 5 +- .../edit_index_pattern/edit_sections.js | 80 --- .../edit_index_pattern/field_controls.html | 19 - .../edit_index_pattern/index.js | 157 +++++- .../index_header/index_header.tsx | 2 +- .../indexed_fields_table.tsx | 10 +- .../scripted_field_table.test.tsx.snap | 10 +- .../scripted_fields_table.tsx | 11 +- .../edit_index_pattern/tabs/index.ts | 20 + .../edit_index_pattern/tabs/tabs.tsx | 267 +++++++++ .../edit_index_pattern/tabs/utils.ts | 146 +++++ test/functional/page_objects/settings_page.ts | 20 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 17 files changed, 861 insertions(+), 792 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_sections.js delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/field_controls.html create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/tabs.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/utils.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html index 0376df6bbdc58..0bf7c7f0bdfbe 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html @@ -6,156 +6,7 @@ role="region" aria-label="{{::'kbn.management.editIndexPattern.detailsAria' | i18n: { defaultMessage: 'Index pattern details' } }}" > - -
- -
-

- - - - - - - - - - - - - - - {{tag.name}} - - - - -

- -
- -
-

- - - - - -

-
- -
- - -
-
- - -
- -
-
- -
-
-
- - -
- -
- - -
-
-
- - -
-
- -
- -
- -
- -
-
- - -
-
- -
- -
-
+
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js deleted file mode 100644 index 3239a17f109e4..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ /dev/null @@ -1,511 +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 _ from 'lodash'; -import { HashRouter } from 'react-router-dom'; -import { IndexHeader } from './index_header'; -import { CreateEditField } from './create_edit_field'; -import { docTitle } from 'ui/doc_title'; -import { KbnUrlProvider } from 'ui/url'; -import { IndicesEditSectionsProvider } from './edit_sections'; -import { fatalError, toastNotifications } from 'ui/notify'; -import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors'; -import uiRoutes from 'ui/routes'; -import { uiModules } from 'ui/modules'; -import template from './edit_index_pattern.html'; -import createEditFieldtemplate from './create_edit_field.html'; -import { fieldWildcardMatcher } from '../../../../../../../../plugins/kibana_utils/public'; -import { subscribeWithScope } from '../../../../../../../../plugins/kibana_legacy/public'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { SourceFiltersTable } from './source_filters_table'; -import { IndexedFieldsTable } from './indexed_fields_table'; -import { ScriptedFieldsTable } from './scripted_fields_table'; -import { i18n } from '@kbn/i18n'; -import { I18nContext } from 'ui/i18n'; -import { npStart } from 'ui/new_platform'; -import { - getEditBreadcrumbs, - getEditFieldBreadcrumbs, - getCreateFieldBreadcrumbs, -} from '../breadcrumbs'; -import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from './constants'; -import { createEditIndexPatternPageStateContainer } from './edit_index_pattern_state_container'; - -const REACT_SOURCE_FILTERS_DOM_ELEMENT_ID = 'reactSourceFiltersTable'; -const REACT_INDEXED_FIELDS_DOM_ELEMENT_ID = 'reactIndexedFieldsTable'; -const REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID = 'reactScriptedFieldsTable'; -const REACT_INDEX_HEADER_DOM_ELEMENT_ID = 'reactIndexHeader'; - -const EDIT_FIELD_PATH = '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}'; - -function updateSourceFiltersTable($scope) { - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID); - if (!node) { - return; - } - - render( - - { - $scope.editSections = $scope.editSectionsProvider( - $scope.indexPattern, - $scope.fieldFilter, - $scope.indexPatternListProvider - ); - $scope.refreshFilters(); - $scope.$apply(); - }} - /> - , - node - ); - }); -} - -function destroySourceFiltersTable() { - const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID); - node && unmountComponentAtNode(node); -} - -function updateScriptedFieldsTable($scope) { - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID); - if (!node) { - return; - } - - render( - - { - $scope.kbnUrl.changePath(EDIT_FIELD_PATH, field); - $scope.$apply(); - }, - getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route), - }} - onRemoveField={() => { - $scope.editSections = $scope.editSectionsProvider( - $scope.indexPattern, - $scope.fieldFilter, - $scope.indexPatternListProvider - ); - $scope.refreshFilters(); - $scope.$apply(); - }} - /> - , - node - ); - }); -} - -function destroyScriptedFieldsTable() { - const node = document.getElementById(REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID); - node && unmountComponentAtNode(node); -} - -function updateIndexedFieldsTable($scope) { - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID); - if (!node) { - return; - } - - render( - - { - $scope.kbnUrl.changePath(EDIT_FIELD_PATH, field); - $scope.$apply(); - }, - getFieldInfo: $scope.getFieldInfo, - }} - /> - , - node - ); - }); -} - -function destroyIndexedFieldsTable() { - const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID); - node && unmountComponentAtNode(node); -} - -function destroyIndexHeader() { - const node = document.getElementById(REACT_INDEX_HEADER_DOM_ELEMENT_ID); - node && unmountComponentAtNode(node); -} - -function renderIndexHeader($scope, config) { - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_INDEX_HEADER_DOM_ELEMENT_ID); - if (!node) { - return; - } - - render( - - - , - node - ); - }); -} - -function handleTabChange($scope, newTab) { - destroyIndexedFieldsTable(); - destroySourceFiltersTable(); - destroyScriptedFieldsTable(); - updateTables($scope, newTab); -} - -function updateTables($scope, currentTab) { - switch (currentTab) { - case TAB_SCRIPTED_FIELDS: - return updateScriptedFieldsTable($scope); - case TAB_INDEXED_FIELDS: - return updateIndexedFieldsTable($scope); - case TAB_SOURCE_FILTERS: - return updateSourceFiltersTable($scope); - } -} - -uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', { - template, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - indexPattern: function($route, Promise, redirectWhenMissing) { - const { indexPatterns } = npStart.plugins.data; - return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( - redirectWhenMissing('/management/kibana/index_patterns') - ); - }, - }, -}); - -uiModules - .get('apps/management') - .controller('managementIndexPatternsEdit', function( - $scope, - $location, - $route, - Promise, - config, - Private - ) { - const { - startSyncingState, - stopSyncingState, - setCurrentTab, - getCurrentTab, - state$, - } = createEditIndexPatternPageStateContainer({ - useHashedUrl: config.get('state:storeInSessionStorage'), - defaultTab: TAB_INDEXED_FIELDS, - }); - - $scope.getCurrentTab = getCurrentTab; - $scope.setCurrentTab = setCurrentTab; - - const stateChangedSub = subscribeWithScope( - $scope, - state$, - { - next: ({ tab }) => { - handleTabChange($scope, tab); - }, - }, - fatalError - ); - - handleTabChange($scope, getCurrentTab()); // setup initial tab depending on initial tab state - - startSyncingState(); // starts syncing state between state container and url - - const destroyState = () => { - stateChangedSub.unsubscribe(); - stopSyncingState(); - }; - - $scope.fieldWildcardMatcher = (...args) => - fieldWildcardMatcher(...args, config.get('metaFields')); - $scope.editSectionsProvider = Private(IndicesEditSectionsProvider); - $scope.kbnUrl = Private(KbnUrlProvider); - $scope.indexPattern = $route.current.locals.indexPattern; - $scope.indexPatternListProvider = npStart.plugins.indexPatternManagement.list; - $scope.indexPattern.tags = npStart.plugins.indexPatternManagement.list.getIndexPatternTags( - $scope.indexPattern, - $scope.indexPattern.id === config.get('defaultIndex') - ); - $scope.getFieldInfo = npStart.plugins.indexPatternManagement.list.getFieldInfo; - docTitle.change($scope.indexPattern.title); - - const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { - return pattern.id !== $scope.indexPattern.id; - }); - - $scope.$watch('indexPattern.fields', function() { - $scope.editSections = $scope.editSectionsProvider( - $scope.indexPattern, - $scope.fieldFilter, - npStart.plugins.indexPatternManagement.list - ); - $scope.refreshFilters(); - $scope.fields = $scope.indexPattern.getNonScriptedFields(); - }); - - $scope.refreshFilters = function() { - const indexedFieldTypes = []; - const scriptedFieldLanguages = []; - $scope.indexPattern.fields.forEach(field => { - if (field.scripted) { - scriptedFieldLanguages.push(field.lang); - } else { - indexedFieldTypes.push(field.type); - } - }); - - $scope.indexedFieldTypes = _.unique(indexedFieldTypes); - $scope.scriptedFieldLanguages = _.unique(scriptedFieldLanguages); - }; - - $scope.changeFilter = function(filter, val) { - $scope[filter] = val || ''; // null causes filter to check for null explicitly - }; - - $scope.$watchCollection('indexPattern.fields', function() { - $scope.conflictFields = $scope.indexPattern.fields.filter(field => field.type === 'conflict'); - }); - - $scope.refreshFields = function() { - const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', { - defaultMessage: 'This action resets the popularity counter of each field.', - }); - const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { - defaultMessage: 'Refresh', - }), - title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { - defaultMessage: 'Refresh field list?', - }), - }; - - npStart.core.overlays - .openConfirm(confirmMessage, confirmModalOptions) - .then(async isConfirmed => { - if (isConfirmed) { - await $scope.indexPattern.init(true); - $scope.fields = $scope.indexPattern.getNonScriptedFields(); - } - }); - }; - - $scope.removePattern = function() { - function doRemove() { - if ($scope.indexPattern.id === config.get('defaultIndex')) { - config.remove('defaultIndex'); - - if (otherPatterns.length) { - config.set('defaultIndex', otherPatterns[0].id); - } - } - - Promise.resolve($scope.indexPattern.destroy()) - .then(function() { - $location.url('/management/kibana/index_patterns'); - }) - .catch(fatalError); - } - - const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { - defaultMessage: 'Delete', - }), - title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { - defaultMessage: 'Delete index pattern?', - }), - }; - - npStart.core.overlays.openConfirm('', confirmModalOptions).then(isConfirmed => { - if (isConfirmed) { - doRemove(); - } - }); - }; - - $scope.setDefaultPattern = function() { - config.set('defaultIndex', $scope.indexPattern.id); - }; - - $scope.setIndexPatternsTimeField = function(field) { - if (field.type !== 'date') { - const errorMessage = i18n.translate('kbn.management.editIndexPattern.notDateErrorMessage', { - defaultMessage: 'That field is a {fieldType} not a date.', - values: { fieldType: field.type }, - }); - toastNotifications.addDanger(errorMessage); - return; - } - $scope.indexPattern.timeFieldName = field.name; - return $scope.indexPattern.save(); - }; - - $scope.$watch('fieldFilter', () => { - $scope.editSections = $scope.editSectionsProvider( - $scope.indexPattern, - $scope.fieldFilter, - npStart.plugins.indexPatternManagement.list - ); - - if ($scope.fieldFilter === undefined) { - return; - } - - updateTables($scope, getCurrentTab()); - }); - - $scope.$watch('indexedFieldTypeFilter', () => { - if ($scope.indexedFieldTypeFilter !== undefined && getCurrentTab() === TAB_INDEXED_FIELDS) { - updateIndexedFieldsTable($scope); - } - }); - - $scope.$watch('scriptedFieldLanguageFilter', () => { - if ( - $scope.scriptedFieldLanguageFilter !== undefined && - getCurrentTab() === TAB_SCRIPTED_FIELDS - ) { - updateScriptedFieldsTable($scope); - } - }); - - $scope.$on('$destroy', () => { - destroyIndexedFieldsTable(); - destroyScriptedFieldsTable(); - destroySourceFiltersTable(); - destroyIndexHeader(); - destroyState(); - }); - - renderIndexHeader($scope, config); - }); - -// routes for create edit field. Will be removed after migartion all component to react. -const REACT_FIELD_EDITOR_ID = 'reactFieldEditor'; -const renderCreateEditField = ($scope, $route, getConfig, $http, fieldFormatEditors) => { - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_FIELD_EDITOR_ID); - if (!node) { - return; - } - - render( - - - - - , - node - ); - }); -}; - -const destroyCreateEditField = () => { - const node = document.getElementById(REACT_FIELD_EDITOR_ID); - node && unmountComponentAtNode(node); -}; - -uiRoutes - .when('/management/kibana/index_patterns/:indexPatternId/field/:fieldName*', { - mode: 'edit', - k7Breadcrumbs: getEditFieldBreadcrumbs, - }) - .when('/management/kibana/index_patterns/:indexPatternId/create-field/', { - mode: 'create', - k7Breadcrumbs: getCreateFieldBreadcrumbs, - }) - .defaults(/management\/kibana\/index_patterns\/[^\/]+\/(field|create-field)(\/|$)/, { - template: createEditFieldtemplate, - mapBreadcrumbs($route, breadcrumbs) { - const { indexPattern } = $route.current.locals; - return breadcrumbs.map(crumb => { - if (crumb.id !== indexPattern.id) { - return crumb; - } - - return { - ...crumb, - display: indexPattern.title, - }; - }); - }, - resolve: { - indexPattern: function($route, Promise, redirectWhenMissing) { - const { indexPatterns } = npStart.plugins.data; - return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( - redirectWhenMissing('/management/kibana/index_patterns') - ); - }, - }, - controllerAs: 'fieldSettings', - controller: function FieldEditorPageController($scope, $route, $http, Private, config) { - const getConfig = (...args) => config.get(...args); - const fieldFormatEditors = Private(RegistryFieldFormatEditorsProvider); - - renderCreateEditField($scope, $route, getConfig, $http, fieldFormatEditors); - - $scope.$on('$destroy', () => { - destroyCreateEditField(); - }); - }, - }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.tsx new file mode 100644 index 0000000000000..e869ac84c2db2 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.tsx @@ -0,0 +1,238 @@ +/* + * 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 { filter } from 'lodash'; +import React, { useEffect, useState, useCallback } from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiBadge, + EuiText, + EuiLink, + EuiIcon, + EuiCallOut, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { IndexPattern, IndexPatternField } from '../../../../../../../../plugins/data/public'; +import { + ChromeDocTitle, + NotificationsStart, + OverlayStart, +} from '../../../../../../../../core/public'; +import { IndexPatternManagementStart } from '../../../../../../../../plugins/index_pattern_management/public'; +import { Tabs } from './tabs'; +import { IndexHeader } from './index_header'; + +interface EditIndexPatternProps extends RouteComponentProps { + indexPattern: IndexPattern; + indexPatterns: IndexPattern[]; + config: Record; + services: { + notifications: NotificationsStart; + docTitle: ChromeDocTitle; + overlays: OverlayStart; + indexPatternManagement: IndexPatternManagementStart; + }; +} + +const mappingAPILink = i18n.translate( + 'kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink', + { + defaultMessage: 'Mapping API', + } +); + +const mappingConflictHeader = i18n.translate( + 'kbn.management.editIndexPattern.mappingConflictHeader', + { + defaultMessage: 'Mapping conflict', + } +); + +const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', { + defaultMessage: 'This action resets the popularity counter of each field.', +}); + +const confirmModalOptionsRefresh = { + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { + defaultMessage: 'Refresh', + }), + title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { + defaultMessage: 'Refresh field list?', + }), +}; + +const confirmModalOptionsDelete = { + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { + defaultMessage: 'Delete', + }), + title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { + defaultMessage: 'Delete index pattern?', + }), +}; + +export const EditIndexPattern = withRouter( + ({ indexPattern, indexPatterns, config, services, history, location }: EditIndexPatternProps) => { + const [fields, setFields] = useState(indexPattern.getNonScriptedFields()); + const [conflictedFields, setConflictedFields] = useState( + indexPattern.fields.filter(field => field.type === 'conflict') + ); + const [defaultIndex, setDefaultIndex] = useState(config.get('defaultIndex')); + const [tags, setTags] = useState([]); + + useEffect(() => { + setFields(indexPattern.getNonScriptedFields()); + setConflictedFields(indexPattern.fields.filter(field => field.type === 'conflict')); + }, [indexPattern, indexPattern.fields]); + + useEffect(() => { + const indexPatternTags = + services.indexPatternManagement.list.getIndexPatternTags( + indexPattern, + indexPattern.id === defaultIndex + ) || []; + setTags(indexPatternTags); + }, [defaultIndex, indexPattern, services.indexPatternManagement.list]); + + const setDefaultPattern = useCallback(() => { + config.set('defaultIndex', indexPattern.id); + setDefaultIndex(indexPattern.id || ''); + }, [config, indexPattern.id]); + + const refreshFields = () => { + services.overlays + .openConfirm(confirmMessage, confirmModalOptionsRefresh) + .then(async isConfirmed => { + if (isConfirmed) { + await indexPattern.init(true); + setFields(indexPattern.getNonScriptedFields()); + } + }); + }; + + const removePattern = () => { + function doRemove() { + if (indexPattern.id === defaultIndex) { + config.remove('defaultIndex'); + const otherPatterns = filter(indexPatterns, pattern => { + return pattern.id !== indexPattern.id; + }); + + if (otherPatterns.length) { + config.set('defaultIndex', otherPatterns[0].id); + } + } + + Promise.resolve(indexPattern.destroy()).then(function() { + history.push('/management/kibana/index_patterns'); + }); + } + + services.overlays.openConfirm('', confirmModalOptionsDelete).then(isConfirmed => { + if (isConfirmed) { + doRemove(); + } + }); + }; + + const timeFilterHeader = i18n.translate('kbn.management.editIndexPattern.timeFilterHeader', { + defaultMessage: "Time Filter field name: '{timeFieldName}'", + values: { timeFieldName: indexPattern.timeFieldName }, + }); + + const mappingConflictLabel = i18n.translate( + 'kbn.management.editIndexPattern.mappingConflictLabel', + { + defaultMessage: + '{conflictFieldsLength, plural, one {A field is} other {# fields are}} defined as several types (string, integer, etc) across the indices that match this pattern. You may still be able to use these conflict fields in parts of Kibana, but they will be unavailable for functions that require Kibana to know their type. Correcting this issue will require reindexing your data.', + values: { conflictFieldsLength: conflictedFields.length }, + } + ); + + services.docTitle.change(indexPattern.title); + + const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0)); + + return ( + + + + + {showTagsSection && ( + + {Boolean(indexPattern.timeFieldName) && ( + + {timeFilterHeader} + + )} + {tags.map((tag: any) => ( + + {tag.name} + + ))} + + )} + + +

+ {indexPattern.title} }} + />{' '} + + {mappingAPILink} + + +

+
+ {conflictedFields.length > 0 && ( + +

{mappingConflictLabel}

+
+ )} +
+ + + +
+ ); + } +); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts index 473417a7aabd6..5723a596f95d5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts @@ -25,7 +25,7 @@ import { } from '../../../../../../../../plugins/kibana_utils/public'; interface IEditIndexPatternState { - tab: string; // TODO: type those 3 tabs with enum, when edit_index_pattern.js migrated to ts + tab: string; } /** @@ -38,7 +38,6 @@ export function createEditIndexPatternPageStateContainer({ defaultTab: string; useHashedUrl: boolean; }) { - // until angular is used as shell - use hash history const history = createHashHistory(); // query param to store app state at const stateStorageKey = '_a'; @@ -78,12 +77,10 @@ export function createEditIndexPatternPageStateContainer({ // makes sure initial url is the same as initial state (this is not really required) kbnUrlStateStorage.set(stateStorageKey, stateContainer.getState(), { replace: true }); - // expose api needed for Controller return { startSyncingState: start, stopSyncingState: stop, setCurrentTab: (newTab: string) => stateContainer.transitions.setTab(newTab), getCurrentTab: () => stateContainer.selectors.tab(), - state$: stateContainer.state$, }; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_sections.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_sections.js deleted file mode 100644 index f0220b2f798e5..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_sections.js +++ /dev/null @@ -1,80 +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 _ from 'lodash'; -import { i18n } from '@kbn/i18n'; - -function filterBy(items, key, filter) { - const lowercaseFilter = (filter || '').toLowerCase(); - return items.filter(item => item[key].toLowerCase().includes(lowercaseFilter)); -} - -function getCounts(fields, sourceFilters, fieldFilter = '') { - const fieldCount = _.countBy(filterBy(fields, 'name', fieldFilter), function(field) { - return field.scripted ? 'scripted' : 'indexed'; - }); - - _.defaults(fieldCount, { - indexed: 0, - scripted: 0, - sourceFilters: sourceFilters ? filterBy(sourceFilters, 'value', fieldFilter).length : 0, - }); - - return fieldCount; -} - -export function IndicesEditSectionsProvider() { - return function(indexPattern, fieldFilter, indexPatternListProvider) { - const totalCount = getCounts(indexPattern.fields, indexPattern.sourceFilters); - const filteredCount = getCounts(indexPattern.fields, indexPattern.sourceFilters, fieldFilter); - - const editSections = []; - - editSections.push({ - title: i18n.translate('kbn.management.editIndexPattern.tabs.fieldsHeader', { - defaultMessage: 'Fields', - }), - index: 'indexedFields', - count: filteredCount.indexed, - totalCount: totalCount.indexed, - }); - - if (indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) { - editSections.push({ - title: i18n.translate('kbn.management.editIndexPattern.tabs.scriptedHeader', { - defaultMessage: 'Scripted fields', - }), - index: 'scriptedFields', - count: filteredCount.scripted, - totalCount: totalCount.scripted, - }); - } - - editSections.push({ - title: i18n.translate('kbn.management.editIndexPattern.tabs.sourceHeader', { - defaultMessage: 'Source filters', - }), - index: 'sourceFilters', - count: filteredCount.sourceFilters, - totalCount: totalCount.sourceFilters, - }); - - return editSections; - }; -} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/field_controls.html b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/field_controls.html deleted file mode 100644 index c14dcd3f3a8d5..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/field_controls.html +++ /dev/null @@ -1,19 +0,0 @@ -
- - - - - -
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js index 6beaee60b3788..e05aea3678fe2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js @@ -17,4 +17,159 @@ * under the License. */ -import './edit_index_pattern'; +import React from 'react'; +import { HashRouter } from 'react-router-dom'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors'; +import uiRoutes from 'ui/routes'; +import { uiModules } from 'ui/modules'; +import { I18nContext } from 'ui/i18n'; +import { npStart } from 'ui/new_platform'; +import template from './edit_index_pattern.html'; +import createEditFieldtemplate from './create_edit_field.html'; +import { + getEditBreadcrumbs, + getEditFieldBreadcrumbs, + getCreateFieldBreadcrumbs, +} from '../breadcrumbs'; +import { EditIndexPattern } from './edit_index_pattern'; +import { CreateEditField } from './create_edit_field'; + +const REACT_EDIT_INDEX_PATTERN_DOM_ELEMENT_ID = 'reactEditIndexPattern'; + +function destroyEditIndexPattern() { + const node = document.getElementById(REACT_EDIT_INDEX_PATTERN_DOM_ELEMENT_ID); + node && unmountComponentAtNode(node); +} + +function renderEditIndexPattern($scope, config, $route) { + $scope.$$postDigest(() => { + const node = document.getElementById(REACT_EDIT_INDEX_PATTERN_DOM_ELEMENT_ID); + if (!node) { + return; + } + + render( + + + + + , + node + ); + }); +} + +uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', { + template, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + indexPattern: function($route, Promise, redirectWhenMissing) { + const { indexPatterns } = npStart.plugins.data; + return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( + redirectWhenMissing('/management/kibana/index_patterns') + ); + }, + }, +}); + +uiModules + .get('apps/management') + .controller('managementIndexPatternsEdit', function($scope, $route, config) { + $scope.$on('$destroy', () => { + destroyEditIndexPattern(); + }); + + renderEditIndexPattern($scope, config, $route); + }); + +// routes for create edit field. Will be removed after migartion all component to react. +const REACT_FIELD_EDITOR_ID = 'reactFieldEditor'; +const renderCreateEditField = ($scope, $route, getConfig, $http, fieldFormatEditors) => { + $scope.$$postDigest(() => { + const node = document.getElementById(REACT_FIELD_EDITOR_ID); + if (!node) { + return; + } + + render( + + + + + , + node + ); + }); +}; + +const destroyCreateEditField = () => { + const node = document.getElementById(REACT_FIELD_EDITOR_ID); + node && unmountComponentAtNode(node); +}; + +uiRoutes + .when('/management/kibana/index_patterns/:indexPatternId/field/:fieldName*', { + mode: 'edit', + k7Breadcrumbs: getEditFieldBreadcrumbs, + }) + .when('/management/kibana/index_patterns/:indexPatternId/create-field/', { + mode: 'create', + k7Breadcrumbs: getCreateFieldBreadcrumbs, + }) + .defaults(/management\/kibana\/index_patterns\/[^\/]+\/(field|create-field)(\/|$)/, { + template: createEditFieldtemplate, + mapBreadcrumbs($route, breadcrumbs) { + const { indexPattern } = $route.current.locals; + return breadcrumbs.map(crumb => { + if (crumb.id !== indexPattern.id) { + return crumb; + } + + return { + ...crumb, + display: indexPattern.title, + }; + }); + }, + resolve: { + indexPattern: function($route, Promise, redirectWhenMissing) { + const { indexPatterns } = npStart.plugins.data; + return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( + redirectWhenMissing('/management/kibana/index_patterns') + ); + }, + }, + controllerAs: 'fieldSettings', + controller: function FieldEditorPageController($scope, $route, $http, Private, config) { + const getConfig = (...args) => config.get(...args); + const fieldFormatEditors = Private(RegistryFieldFormatEditorsProvider); + + renderCreateEditField($scope, $route, getConfig, $http, fieldFormatEditors); + + $scope.$on('$destroy', () => { + destroyCreateEditField(); + }); + }, + }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx index deac85d9a32e9..a06671ef6a470 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx @@ -92,7 +92,7 @@ export function IndexHeader({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 7c2bb565615d7..c69063967b1e2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -19,7 +19,11 @@ import React, { Component } from 'react'; import { createSelector } from 'reselect'; -import { IndexPatternField, IIndexPattern } from '../../../../../../../../../plugins/data/public'; +import { + IndexPatternField, + IIndexPattern, + IFieldType, +} from '../../../../../../../../../plugins/data/public'; import { Table } from './components/table'; import { getFieldFormat } from './lib'; import { IndexedFieldItem } from './types'; @@ -31,7 +35,7 @@ interface IndexedFieldsTableProps { indexedFieldTypeFilter?: string; helpers: { redirectToRoute: (obj: any) => void; - getFieldInfo: (indexPattern: IIndexPattern, field: string) => string[]; + getFieldInfo: (indexPattern: IIndexPattern, field: IFieldType) => string[]; }; fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean; } @@ -76,7 +80,7 @@ export class IndexedFieldsTable extends Component< indexPattern: field.indexPattern, format: getFieldFormat(indexPattern, field.name), excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false, - info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field.name), + info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field), }; })) || [] diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap index 569b75c848c52..202b09ddc6066 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap @@ -3,7 +3,7 @@ exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = `
void; } @@ -136,14 +136,19 @@ export class ScriptedFieldsTable extends Component< }; render() { - const { helpers, indexPattern } = this.props; + const { indexPattern } = this.props; const { fieldToDelete, deprecatedLangsInUse } = this.state; const items = this.getFilteredItems(); return ( <> -
+
{ + indexPattern: IndexPattern; + config: Record; + fields: IndexPatternField[]; + services: { + indexPatternManagement: IndexPatternManagementStart; + }; +} + +const filterAriaLabel = i18n.translate('kbn.management.editIndexPattern.fields.filterAria', { + defaultMessage: 'Filter', +}); + +const filterPlaceholder = i18n.translate( + 'kbn.management.editIndexPattern.fields.filterPlaceholder', + { + defaultMessage: 'Filter', + } +); + +export function Tabs({ config, indexPattern, fields, services, history, location }: TabsProps) { + const [fieldFilter, setFieldFilter] = useState(''); + const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState(''); + const [scriptedFieldLanguageFilter, setScriptedFieldLanguageFilter] = useState(''); + const [indexedFieldTypes, setIndexedFieldType] = useState([]); + const [scriptedFieldLanguages, setScriptedFieldLanguages] = useState([]); + const [syncingStateFunc, setSyncingStateFunc] = useState({ + getCurrentTab: () => TAB_INDEXED_FIELDS, + }); + + const refreshFilters = useCallback(() => { + const tempIndexedFieldTypes: string[] = []; + const tempScriptedFieldLanguages: string[] = []; + indexPattern.fields.forEach(field => { + if (field.scripted) { + if (field.lang) { + tempScriptedFieldLanguages.push(field.lang); + } + } else { + tempIndexedFieldTypes.push(field.type); + } + }); + + setIndexedFieldType(convertToEuiSelectOption(tempIndexedFieldTypes, 'indexedFiledTypes')); + setScriptedFieldLanguages( + convertToEuiSelectOption(tempScriptedFieldLanguages, 'scriptedFieldLanguages') + ); + }, [indexPattern]); + + useEffect(() => { + refreshFilters(); + }, [indexPattern, indexPattern.fields, refreshFilters]); + + const fieldWildcardMatcherDecorated = useCallback( + (filters: string[]) => fieldWildcardMatcher(filters, config.get('metaFields')), + [config] + ); + + const getFilterSection = useCallback( + (type: string) => { + return ( + + + setFieldFilter(e.target.value)} + data-test-subj="indexPatternFieldFilter" + aria-label={filterAriaLabel} + /> + + {type === TAB_INDEXED_FIELDS && indexedFieldTypes.length > 0 && ( + + setIndexedFieldTypeFilter(e.target.value)} + data-test-subj="indexedFieldTypeFilterDropdown" + /> + + )} + {type === TAB_SCRIPTED_FIELDS && scriptedFieldLanguages.length > 0 && ( + + setScriptedFieldLanguageFilter(e.target.value)} + data-test-subj="scriptedFieldLanguageFilterDropdown" + /> + + )} + + ); + }, + [ + fieldFilter, + indexedFieldTypeFilter, + indexedFieldTypes, + scriptedFieldLanguageFilter, + scriptedFieldLanguages, + ] + ); + + const getContent = useCallback( + (type: string) => { + switch (type) { + case TAB_INDEXED_FIELDS: + return ( + + + {getFilterSection(type)} + + { + history.push(getPath(field)); + }, + getFieldInfo: services.indexPatternManagement.list.getFieldInfo, + }} + /> + + ); + case TAB_SCRIPTED_FIELDS: + return ( + + + {getFilterSection(type)} + + { + history.push(getPath(field)); + }, + }} + onRemoveField={refreshFilters} + /> + + ); + case TAB_SOURCE_FILTERS: + return ( + + + {getFilterSection(type)} + + + + ); + } + }, + [ + fieldFilter, + fieldWildcardMatcherDecorated, + fields, + getFilterSection, + history, + indexPattern, + indexedFieldTypeFilter, + refreshFilters, + scriptedFieldLanguageFilter, + services.indexPatternManagement.list.getFieldInfo, + ] + ); + + const euiTabs: EuiTabbedContentTab[] = useMemo( + () => + getTabs(indexPattern, fieldFilter, services.indexPatternManagement.list).map( + (tab: Pick) => { + return { + ...tab, + content: getContent(tab.id), + }; + } + ), + [fieldFilter, getContent, indexPattern, services.indexPatternManagement.list] + ); + + const [selectedTabId, setSelectedTabId] = useState(euiTabs[0].id); + + useEffect(() => { + const { + startSyncingState, + stopSyncingState, + setCurrentTab, + getCurrentTab, + } = createEditIndexPatternPageStateContainer({ + useHashedUrl: config.get('state:storeInSessionStorage'), + defaultTab: TAB_INDEXED_FIELDS, + }); + + startSyncingState(); + setSyncingStateFunc({ + setCurrentTab, + getCurrentTab, + }); + setSelectedTabId(getCurrentTab()); + + return () => { + stopSyncingState(); + }; + }, [config]); + + return ( + tab.id === selectedTabId)} + onTabClick={tab => { + setSelectedTabId(tab.id); + syncingStateFunc.setCurrentTab(tab.id); + }} + /> + ); +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/utils.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/utils.ts new file mode 100644 index 0000000000000..bdb1436c37efb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/utils.ts @@ -0,0 +1,146 @@ +/* + * 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 { Dictionary, countBy, defaults, unique } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { IndexPattern, IndexPatternField } from '../../../../../../../../../plugins/data/public'; +import { IndexPatternManagementStart } from '../../../../../../../../../plugins/index_pattern_management/public'; +import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../constants'; + +function filterByName(items: IndexPatternField[], filter: string) { + const lowercaseFilter = (filter || '').toLowerCase(); + return items.filter(item => item.name.toLowerCase().includes(lowercaseFilter)); +} + +function getCounts( + fields: IndexPatternField[], + sourceFilters: { + excludes: string[]; + }, + fieldFilter = '' +) { + const fieldCount = countBy(filterByName(fields, fieldFilter), function(field) { + return field.scripted ? 'scripted' : 'indexed'; + }); + + defaults(fieldCount, { + indexed: 0, + scripted: 0, + sourceFilters: sourceFilters.excludes + ? sourceFilters.excludes.filter(value => + value.toLowerCase().includes(fieldFilter.toLowerCase()) + ).length + : 0, + }); + + return fieldCount; +} + +function getTitle(type: string, filteredCount: Dictionary, totalCount: Dictionary) { + let title = ''; + switch (type) { + case 'indexed': + title = i18n.translate('kbn.management.editIndexPattern.tabs.fieldsHeader', { + defaultMessage: 'Fields', + }); + break; + case 'scripted': + title = i18n.translate('kbn.management.editIndexPattern.tabs.scriptedHeader', { + defaultMessage: 'Scripted fields', + }); + break; + case 'sourceFilters': + title = i18n.translate('kbn.management.editIndexPattern.tabs.sourceHeader', { + defaultMessage: 'Source filters', + }); + break; + } + const count = ` (${ + filteredCount[type] === totalCount[type] + ? filteredCount[type] + : filteredCount[type] + ' / ' + totalCount[type] + })`; + return title + count; +} + +export function getTabs( + indexPattern: IndexPattern, + fieldFilter: string, + indexPatternListProvider: IndexPatternManagementStart['list'] +) { + const totalCount = getCounts(indexPattern.fields, indexPattern.getSourceFiltering()); + const filteredCount = getCounts( + indexPattern.fields, + indexPattern.getSourceFiltering(), + fieldFilter + ); + + const tabs = []; + + tabs.push({ + name: getTitle('indexed', filteredCount, totalCount), + id: TAB_INDEXED_FIELDS, + }); + + if (indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) { + tabs.push({ + name: getTitle('scripted', filteredCount, totalCount), + id: TAB_SCRIPTED_FIELDS, + }); + } + + tabs.push({ + name: getTitle('sourceFilters', filteredCount, totalCount), + id: TAB_SOURCE_FILTERS, + }); + + return tabs; +} + +export function getPath(field: IndexPatternField) { + return `/management/kibana/index_patterns/${field.indexPattern?.id}/field/${field.name}`; +} + +const allTypesDropDown = i18n.translate('kbn.management.editIndexPattern.fields.allTypesDropDown', { + defaultMessage: 'All field types', +}); + +const allLangsDropDown = i18n.translate('kbn.management.editIndexPattern.fields.allLangsDropDown', { + defaultMessage: 'All languages', +}); + +export function convertToEuiSelectOption(options: string[], type: string) { + const euiOptions = + options.length > 0 + ? [ + { + value: '', + text: type === 'scriptedFieldLanguages' ? allLangsDropDown : allTypesDropDown, + }, + ] + : []; + return euiOptions.concat( + unique(options).map(option => { + return { + value: option, + text: option, + }; + }) + ); +} diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 6dcd017335c85..8864eda3823ef 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -206,15 +206,17 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider async getFieldsTabCount() { return retry.try(async () => { - const text = await testSubjects.getVisibleText('tab-count-indexedFields'); - return text.replace(/\((.*)\)/, '$1'); + const indexedFieldsTab = await find.byCssSelector('#indexedFields .euiTab__content'); + const text = await indexedFieldsTab.getVisibleText(); + return text.split(/[()]/)[1]; }); } async getScriptedFieldsTabCount() { return await retry.try(async () => { - const theText = await testSubjects.getVisibleText('tab-count-scriptedFields'); - return theText.replace(/\((.*)\)/, '$1'); + const scriptedFieldsTab = await find.byCssSelector('#scriptedFields .euiTab__content'); + const text = await scriptedFieldsTab.getVisibleText(); + return text.split(/[()]/)[1]; }); } @@ -241,13 +243,13 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider async setFieldTypeFilter(type: string) { await find.clickByCssSelector( - 'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]' + 'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[value="' + type + '"]' ); } async setScriptedFieldLanguageFilter(language: string) { await find.clickByCssSelector( - 'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + + 'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[value="' + language + '"]' ); @@ -412,17 +414,17 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider async clickFieldsTab() { log.debug('click Fields tab'); - await testSubjects.click('tab-indexFields'); + await find.clickByCssSelector('#indexedFields'); } async clickScriptedFieldsTab() { log.debug('click Scripted Fields tab'); - await testSubjects.click('tab-scriptedFields'); + await find.clickByCssSelector('#scriptedFields'); } async clickSourceFiltersTab() { log.debug('click Source Filters tab'); - await testSubjects.click('tab-sourceFilters'); + await find.clickByCssSelector('#sourceFilters'); } async editScriptedField(name: string) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 87e8e2e7a41e2..909a736220d17 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2186,10 +2186,8 @@ "kbn.management.createIndexPatternHeader": "{indexPatternName} の作成", "kbn.management.createIndexPatternLabel": "Kibana は、可視化などを目的に Elasticsearch インデックスからデータを取得するために、インデックスパターンを使用します。", "kbn.management.editIndexPattern.deleteButton": "削除", - "kbn.management.editIndexPattern.deleteFieldButton": "削除", "kbn.management.editIndexPattern.deleteHeader": "インデックスパターンを削除しますか?", "kbn.management.editIndexPattern.detailsAria": "インデックスパターンの詳細", - "kbn.management.editIndexPattern.editFieldButton": "編集", "kbn.management.editIndexPattern.fields.allLangsDropDown": "すべての言語", "kbn.management.editIndexPattern.fields.allTypesDropDown": "すべてのフィールドタイプ", "kbn.management.editIndexPattern.fields.filterAria": "フィルター", @@ -2215,7 +2213,6 @@ "kbn.management.editIndexPattern.fields.table.typeHeader": "タイプ", "kbn.management.editIndexPattern.mappingConflictHeader": "マッピングの矛盾", "kbn.management.editIndexPattern.mappingConflictLabel": "{conflictFieldsLength, plural, one {フィールドが} other {# フィールドが}}このパターンと一致するインデックスの間で異なるタイプ (文字列、整数など) に定義されています。これらの矛盾したフィールドは Kibana の一部で使用できますが、Kibana がタイプを把握しなければならない機能には使用できません。この問題を修正するにはデータのレンダリングが必要です。", - "kbn.management.editIndexPattern.notDateErrorMessage": "このフィールドは日付ではなく {fieldType} です。", "kbn.management.editIndexPattern.refreshAria": "フィールドリストを再度読み込みます", "kbn.management.editIndexPattern.refreshButton": "更新", "kbn.management.editIndexPattern.refreshHeader": "フィールドリストを更新しますか?", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4a5ed3291a915..0ebef9f8f1ef9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2187,10 +2187,8 @@ "kbn.management.createIndexPatternHeader": "创建 {indexPatternName}", "kbn.management.createIndexPatternLabel": "Kibana 使用索引模式从 Elasticsearch 索引中检索数据,以实现诸如可视化等功能。", "kbn.management.editIndexPattern.deleteButton": "删除", - "kbn.management.editIndexPattern.deleteFieldButton": "删除", "kbn.management.editIndexPattern.deleteHeader": "删除索引模式?", "kbn.management.editIndexPattern.detailsAria": "索引模式详细信息", - "kbn.management.editIndexPattern.editFieldButton": "编辑", "kbn.management.editIndexPattern.fields.allLangsDropDown": "所有语言", "kbn.management.editIndexPattern.fields.allTypesDropDown": "所有字段类型", "kbn.management.editIndexPattern.fields.filterAria": "筛选", @@ -2216,7 +2214,6 @@ "kbn.management.editIndexPattern.fields.table.typeHeader": "类型", "kbn.management.editIndexPattern.mappingConflictHeader": "映射冲突", "kbn.management.editIndexPattern.mappingConflictLabel": "匹配此模式的各个索引中{conflictFieldsLength, plural, one {一个字段已} other {# 个字段已}}定义为若干类型(字符串、整数等)。您仍能够在 Kibana 的各个部分中使用这些冲突类型,但它们将无法用于需要 Kibana 知道其类型的函数。要解决此问题,需要重新索引您的数据。", - "kbn.management.editIndexPattern.notDateErrorMessage": "该字段是{fieldType},不是日期。", "kbn.management.editIndexPattern.refreshAria": "重新加载字段列表", "kbn.management.editIndexPattern.refreshButton": "刷新", "kbn.management.editIndexPattern.refreshHeader": "刷新字段列表?", From 6d099d0a4f9c78c105285f4b7c261f24d993c954 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 24 Apr 2020 14:55:43 +0200 Subject: [PATCH 03/13] Migrate graph_workspace saved object registration to Kibana platform (#64157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Migrate graph_workspace saved object registration to Kibana platform * Remove legacy plugin 🎉 --- src/dev/precommit_hook/casing_check_config.js | 2 - x-pack/.i18nrc.json | 2 +- x-pack/index.js | 2 - x-pack/legacy/plugins/graph/index.ts | 32 ----- x-pack/legacy/plugins/graph/mappings.json | 31 ----- x-pack/legacy/plugins/graph/migrations.js | 41 ------- .../legacy/plugins/graph/migrations.test.js | 102 ---------------- x-pack/plugins/graph/server/plugin.ts | 2 + .../server/saved_objects/graph_workspace.ts | 43 +++++++ .../graph/server/saved_objects/index.ts | 6 + .../server/saved_objects/migrations.test.ts | 111 ++++++++++++++++++ .../graph/server/saved_objects/migrations.ts | 40 +++++++ 12 files changed, 203 insertions(+), 211 deletions(-) delete mode 100644 x-pack/legacy/plugins/graph/index.ts delete mode 100644 x-pack/legacy/plugins/graph/mappings.json delete mode 100644 x-pack/legacy/plugins/graph/migrations.js delete mode 100644 x-pack/legacy/plugins/graph/migrations.test.js create mode 100644 x-pack/plugins/graph/server/saved_objects/graph_workspace.ts create mode 100644 x-pack/plugins/graph/server/saved_objects/index.ts create mode 100644 x-pack/plugins/graph/server/saved_objects/migrations.test.ts create mode 100644 x-pack/plugins/graph/server/saved_objects/migrations.ts diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index a75a6997a8cb2..fc95288eabed8 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -159,8 +159,6 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'webpackShims/elasticsearch-browser.js', 'webpackShims/moment-timezone.js', 'webpackShims/ui-bootstrap.js', - 'x-pack/legacy/plugins/graph/public/graphClientWorkspace.js', - 'x-pack/legacy/plugins/graph/public/angular-venn-simple.js', 'x-pack/legacy/plugins/index_management/public/lib/editSettings.js', 'x-pack/legacy/plugins/license_management/public/store/reducers/licenseManagement.js', 'x-pack/legacy/plugins/monitoring/public/components/sparkline/__mocks__/plugins/xpack_main/jquery_flot.js', diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 8e5563e4ff674..a8e50e017102f 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -15,7 +15,7 @@ "xpack.endpoint": "plugins/endpoint", "xpack.features": "plugins/features", "xpack.fileUpload": "plugins/file_upload", - "xpack.graph": ["legacy/plugins/graph", "plugins/graph"], + "xpack.graph": ["plugins/graph"], "xpack.grokDebugger": "plugins/grokdebugger", "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", diff --git a/x-pack/index.js b/x-pack/index.js index 43ae5c3e5c5dd..82a35f1710361 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -5,7 +5,6 @@ */ import { xpackMain } from './legacy/plugins/xpack_main'; -import { graph } from './legacy/plugins/graph'; import { monitoring } from './legacy/plugins/monitoring'; import { reporting } from './legacy/plugins/reporting'; import { security } from './legacy/plugins/security'; @@ -32,7 +31,6 @@ import { triggersActionsUI } from './legacy/plugins/triggers_actions_ui'; module.exports = function(kibana) { return [ xpackMain(kibana), - graph(kibana), monitoring(kibana), reporting(kibana), spaces(kibana), diff --git a/x-pack/legacy/plugins/graph/index.ts b/x-pack/legacy/plugins/graph/index.ts deleted file mode 100644 index 5c7f8fa46c18b..0000000000000 --- a/x-pack/legacy/plugins/graph/index.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. - */ - -// @ts-ignore -import migrations from './migrations'; -import mappings from './mappings.json'; -import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; - -export const graph: LegacyPluginInitializer = kibana => { - return new kibana.Plugin({ - id: 'graph', - configPrefix: 'xpack.graph', - require: ['kibana', 'elasticsearch', 'xpack_main'], - uiExports: { - mappings, - migrations, - }, - - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - canEditDrillDownUrls: Joi.boolean().default(true), - savePolicy: Joi.string() - .valid(['config', 'configAndDataWithConsent', 'configAndData', 'none']) - .default('configAndData'), - }).default(); - }, - }); -}; diff --git a/x-pack/legacy/plugins/graph/mappings.json b/x-pack/legacy/plugins/graph/mappings.json deleted file mode 100644 index f1950c459eee5..0000000000000 --- a/x-pack/legacy/plugins/graph/mappings.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - } -} diff --git a/x-pack/legacy/plugins/graph/migrations.js b/x-pack/legacy/plugins/graph/migrations.js deleted file mode 100644 index 0cefe6217b45d..0000000000000 --- a/x-pack/legacy/plugins/graph/migrations.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; - -export default { - 'graph-workspace': { - '7.0.0': doc => { - // Set new "references" attribute - doc.references = doc.references || []; - // Migrate index pattern - const wsState = get(doc, 'attributes.wsState'); - if (typeof wsState !== 'string') { - return doc; - } - let state; - try { - state = JSON.parse(JSON.parse(wsState)); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return doc; - } - const { indexPattern } = state; - if (!indexPattern) { - return doc; - } - state.indexPatternRefName = 'indexPattern_0'; - delete state.indexPattern; - doc.attributes.wsState = JSON.stringify(JSON.stringify(state)); - doc.references.push({ - name: 'indexPattern_0', - type: 'index-pattern', - id: indexPattern, - }); - return doc; - }, - }, -}; diff --git a/x-pack/legacy/plugins/graph/migrations.test.js b/x-pack/legacy/plugins/graph/migrations.test.js deleted file mode 100644 index 93162d94857ce..0000000000000 --- a/x-pack/legacy/plugins/graph/migrations.test.js +++ /dev/null @@ -1,102 +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 migrations from './migrations'; - -describe('graph-workspace', () => { - describe('7.0.0', () => { - const migration = migrations['graph-workspace']['7.0.0']; - - test('returns doc on empty object', () => { - expect(migration({})).toMatchInlineSnapshot(` -Object { - "references": Array [], -} -`); - }); - - test('returns doc when wsState is not a string', () => { - const doc = { - id: '1', - attributes: { - wsState: true, - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "wsState": true, - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('returns doc when wsState is not valid JSON', () => { - const doc = { - id: '1', - attributes: { - wsState: '123abc', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "wsState": "123abc", - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('returns doc when "indexPattern" is missing from wsState', () => { - const doc = { - id: '1', - attributes: { - wsState: JSON.stringify(JSON.stringify({ foo: true })), - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "wsState": "\\"{\\\\\\"foo\\\\\\":true}\\"", - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('extract "indexPattern" attribute from doc', () => { - const doc = { - id: '1', - attributes: { - wsState: JSON.stringify(JSON.stringify({ foo: true, indexPattern: 'pattern*' })), - bar: true, - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "bar": true, - "wsState": "\\"{\\\\\\"foo\\\\\\":true,\\\\\\"indexPatternRefName\\\\\\":\\\\\\"indexPattern_0\\\\\\"}\\"", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "indexPattern_0", - "type": "index-pattern", - }, - ], -} -`); - }); - }); -}); diff --git a/x-pack/plugins/graph/server/plugin.ts b/x-pack/plugins/graph/server/plugin.ts index a169953d5a10b..141d5d0ea8db4 100644 --- a/x-pack/plugins/graph/server/plugin.ts +++ b/x-pack/plugins/graph/server/plugin.ts @@ -13,6 +13,7 @@ import { registerExploreRoute } from './routes/explore'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { registerSampleData } from './sample_data'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; +import { graphWorkspace } from './saved_objects'; export class GraphPlugin implements Plugin { private licenseState: LicenseState | null = null; @@ -32,6 +33,7 @@ export class GraphPlugin implements Plugin { const licenseState = new LicenseState(); licenseState.start(licensing.license$); this.licenseState = licenseState; + core.savedObjects.registerType(graphWorkspace); if (home) { registerSampleData(home.sampleData, licenseState); diff --git a/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts b/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts new file mode 100644 index 0000000000000..8e8cb64aac1b9 --- /dev/null +++ b/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts @@ -0,0 +1,43 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; +import { graphMigrations } from './migrations'; + +export const graphWorkspace: SavedObjectsType = { + name: 'graph-workspace', + namespaceType: 'single', + hidden: false, + migrations: graphMigrations, + mappings: { + properties: { + description: { + type: 'text', + }, + kibanaSavedObjectMeta: { + properties: { + searchSourceJSON: { + type: 'text', + }, + }, + }, + numLinks: { + type: 'integer', + }, + numVertices: { + type: 'integer', + }, + title: { + type: 'text', + }, + version: { + type: 'integer', + }, + wsState: { + type: 'text', + }, + }, + }, +}; diff --git a/x-pack/plugins/graph/server/saved_objects/index.ts b/x-pack/plugins/graph/server/saved_objects/index.ts new file mode 100644 index 0000000000000..67d1501950175 --- /dev/null +++ b/x-pack/plugins/graph/server/saved_objects/index.ts @@ -0,0 +1,6 @@ +/* + * 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 { graphWorkspace } from './graph_workspace'; diff --git a/x-pack/plugins/graph/server/saved_objects/migrations.test.ts b/x-pack/plugins/graph/server/saved_objects/migrations.test.ts new file mode 100644 index 0000000000000..ecf1f3ca3b69e --- /dev/null +++ b/x-pack/plugins/graph/server/saved_objects/migrations.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { graphMigrations } from './migrations'; +import { SavedObjectUnsanitizedDoc } from 'kibana/server'; + +describe('graph-workspace', () => { + describe('7.0.0', () => { + const migration = graphMigrations['7.0.0']; + + test('returns doc on empty object', () => { + expect(migration({} as SavedObjectUnsanitizedDoc)).toMatchInlineSnapshot(` + Object { + "references": Array [], + } + `); + }); + + test('returns doc when wsState is not a string', () => { + const doc = { + id: '1', + type: 'graph-workspace', + attributes: { + wsState: true, + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "wsState": true, + }, + "id": "1", + "references": Array [], + "type": "graph-workspace", + } + `); + }); + + test('returns doc when wsState is not valid JSON', () => { + const doc = { + id: '1', + type: 'graph-workspace', + attributes: { + wsState: '123abc', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "wsState": "123abc", + }, + "id": "1", + "references": Array [], + "type": "graph-workspace", + } + `); + }); + + test('returns doc when "indexPattern" is missing from wsState', () => { + const doc = { + id: '1', + type: 'graph-workspace', + attributes: { + wsState: JSON.stringify(JSON.stringify({ foo: true })), + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "wsState": "\\"{\\\\\\"foo\\\\\\":true}\\"", + }, + "id": "1", + "references": Array [], + "type": "graph-workspace", + } + `); + }); + + test('extract "indexPattern" attribute from doc', () => { + const doc = { + id: '1', + type: 'graph-workspace', + attributes: { + wsState: JSON.stringify(JSON.stringify({ foo: true, indexPattern: 'pattern*' })), + bar: true, + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "bar": true, + "wsState": "\\"{\\\\\\"foo\\\\\\":true,\\\\\\"indexPatternRefName\\\\\\":\\\\\\"indexPattern_0\\\\\\"}\\"", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "indexPattern_0", + "type": "index-pattern", + }, + ], + "type": "graph-workspace", + } + `); + }); + }); +}); diff --git a/x-pack/plugins/graph/server/saved_objects/migrations.ts b/x-pack/plugins/graph/server/saved_objects/migrations.ts new file mode 100644 index 0000000000000..e77d2ea0fb7c9 --- /dev/null +++ b/x-pack/plugins/graph/server/saved_objects/migrations.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { SavedObjectUnsanitizedDoc } from 'kibana/server'; + +export const graphMigrations = { + '7.0.0': (doc: SavedObjectUnsanitizedDoc) => { + // Set new "references" attribute + doc.references = doc.references || []; + // Migrate index pattern + const wsState = get(doc, 'attributes.wsState'); + if (typeof wsState !== 'string') { + return doc; + } + let state; + try { + state = JSON.parse(JSON.parse(wsState)); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc; + } + const { indexPattern } = state; + if (!indexPattern) { + return doc; + } + state.indexPatternRefName = 'indexPattern_0'; + delete state.indexPattern; + doc.attributes.wsState = JSON.stringify(JSON.stringify(state)); + doc.references.push({ + name: 'indexPattern_0', + type: 'index-pattern', + id: indexPattern, + }); + return doc; + }, +}; From 0dcefe8dbcf22dd7b437f24548c9b1b6eaffea7c Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Fri, 24 Apr 2020 09:01:23 -0400 Subject: [PATCH 04/13] [Fleet] Fix agent status count to not include unenrolled agents (#64106) --- x-pack/plugins/ingest_manager/server/services/agents/status.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.ts b/x-pack/plugins/ingest_manager/server/services/agents/status.ts index 001b6d01f078e..fce989cea3248 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/status.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/status.ts @@ -67,7 +67,7 @@ export async function getAgentStatusForConfig( AgentStatusKueryHelper.buildKueryForOfflineAgents(), ].map(kuery => listAgents(soClient, { - showInactive: true, + showInactive: false, perPage: 0, page: 1, kuery: configId From 9bda6bde838d71f00c0f54dfb5f26f4ac9b2ba2a Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Fri, 24 Apr 2020 16:20:32 +0300 Subject: [PATCH 05/13] Move ensureDefaultIndexPattern into data plugin (#63100) * Move ensure_default_index_pattern into data plugin * Update docs to include ensureDefaultIndexPattern * Fix translations * Move helper into index_patterns service * Update docs * Remove mock * Add mock Co-authored-by: Elastic Machine --- .../kibana/public/discover/kibana_services.ts | 6 +- .../discover/np_ready/angular/discover.js | 3 +- .../create_index_pattern_wizard.test.tsx.snap | 1 + .../public/application/legacy_app.js | 9 +- .../ensure_default_index_pattern.tsx | 98 +++++++++++++++++++ .../index_patterns/index_patterns.test.ts | 6 +- .../index_patterns/index_patterns.ts | 15 +-- src/plugins/data/public/mocks.ts | 1 + src/plugins/data/public/plugin.ts | 2 +- .../history/ensure_default_index_pattern.tsx | 98 ------------------- .../kibana_utils/public/history/index.ts | 1 - src/plugins/kibana_utils/public/index.ts | 2 +- .../saved_objects_table.test.tsx.snap | 1 + .../public/application/legacy_app.js | 20 ++-- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 16 files changed, 133 insertions(+), 134 deletions(-) create mode 100644 src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx delete mode 100644 src/plugins/kibana_utils/public/history/ensure_default_index_pattern.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 3aa552b1da07d..77664e87a3279 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -59,11 +59,7 @@ export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ export const getHistory = _.once(() => createHashHistory()); export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search; -export { - unhashUrl, - redirectWhenMissing, - ensureDefaultIndexPattern, -} from '../../../../../plugins/kibana_utils/public'; +export { unhashUrl, redirectWhenMissing } from '../../../../../plugins/kibana_utils/public'; export { formatMsg, formatStack, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 567cfda45cc0d..c1de704d1c00a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -49,7 +49,6 @@ import { subscribeWithScope, tabifyAggResponse, getAngularModule, - ensureDefaultIndexPattern, redirectWhenMissing, } from '../../kibana_services'; @@ -118,7 +117,7 @@ app.config($routeProvider => { savedObjects: function($route, Promise) { const history = getHistory(); const savedSearchId = $route.current.params.id; - return ensureDefaultIndexPattern(core, data, history).then(() => { + return data.indexPatterns.ensureDefaultIndexPattern(history).then(() => { const { appStateContainer } = getState({ history }); const { index } = appStateContainer.getState(); return Promise.props({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap index 09a06bd8827ce..ed65db10e0acb 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap @@ -149,6 +149,7 @@ exports[`CreateIndexPatternWizard renders time field step when step is set to 2 indexPatternsService={ Object { "clearCache": [MockFunction], + "ensureDefaultIndexPattern": [MockFunction], "get": [MockFunction], "make": [Function], } diff --git a/src/plugins/dashboard/public/application/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js index 10243dbf2f979..31225530b10b9 100644 --- a/src/plugins/dashboard/public/application/legacy_app.js +++ b/src/plugins/dashboard/public/application/legacy_app.js @@ -28,7 +28,6 @@ import { initDashboardAppDirective } from './dashboard_app'; import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants'; import { createKbnUrlStateStorage, - ensureDefaultIndexPattern, redirectWhenMissing, InvalidJSONProperty, SavedObjectNotFound, @@ -138,7 +137,7 @@ export function initDashboardApp(app, deps) { }, resolve: { dash: function($route, history) { - return ensureDefaultIndexPattern(deps.core, deps.data, history).then(() => { + return deps.data.indexPatterns.ensureDefaultIndexPattern(history).then(() => { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -173,7 +172,8 @@ export function initDashboardApp(app, deps) { requireUICapability: 'dashboard.createNew', resolve: { dash: history => - ensureDefaultIndexPattern(deps.core, deps.data, history) + deps.data.indexPatterns + .ensureDefaultIndexPattern(history) .then(() => deps.savedDashboards.get()) .catch( redirectWhenMissing({ @@ -194,7 +194,8 @@ export function initDashboardApp(app, deps) { dash: function($route, history) { const id = $route.current.params.id; - return ensureDefaultIndexPattern(deps.core, deps.data, history) + return deps.data.indexPatterns + .ensureDefaultIndexPattern(history) .then(() => deps.savedDashboards.get(id)) .then(savedDashboard => { deps.chrome.recentlyAccessed.add( diff --git a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx new file mode 100644 index 0000000000000..6b71739862f62 --- /dev/null +++ b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx @@ -0,0 +1,98 @@ +/* + * 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 { contains } from 'lodash'; +import React from 'react'; +import { History } from 'history'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut } from '@elastic/eui'; +import { CoreStart } from 'kibana/public'; +import { toMountPoint } from '../../../../kibana_react/public'; +import { IndexPatternsContract } from './index_patterns'; + +export type EnsureDefaultIndexPattern = (history: History) => Promise | undefined; + +export const createEnsureDefaultIndexPattern = (core: CoreStart) => { + let bannerId: string; + let timeoutId: NodeJS.Timeout | undefined; + + /** + * Checks whether a default index pattern is set and exists and defines + * one otherwise. + * + * If there are no index patterns, redirect to management page and show + * banner. In this case the promise returned from this function will never + * resolve to wait for the URL change to happen. + */ + return async function ensureDefaultIndexPattern(this: IndexPatternsContract, history: History) { + const patterns = await this.getIds(); + let defaultId = core.uiSettings.get('defaultIndex'); + let defined = !!defaultId; + const exists = contains(patterns, defaultId); + + if (defined && !exists) { + core.uiSettings.remove('defaultIndex'); + defaultId = defined = false; + } + + if (defined) { + return; + } + + // If there is any index pattern created, set the first as default + if (patterns.length >= 1) { + defaultId = patterns[0]; + core.uiSettings.set('defaultIndex', defaultId); + } else { + const canManageIndexPatterns = core.application.capabilities.management.kibana.index_patterns; + const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; + + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Avoid being hostile to new users who don't have an index pattern setup yet + // give them a friendly info message instead of a terse error message + bannerId = core.overlays.banners.replace( + bannerId, + toMountPoint( + + ) + ); + + // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around + timeoutId = setTimeout(() => { + core.overlays.banners.remove(bannerId); + timeoutId = undefined; + }, 15000); + + history.push(redirectTarget); + + // return never-resolving promise to stop resolving and wait for the url change + return new Promise(() => {}); + } + }; +}; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index c429431b632bd..cf1f83d0e28cb 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -21,9 +21,9 @@ import { IndexPatternsService } from './index_patterns'; import { SavedObjectsClientContract, - IUiSettingsClient, HttpSetup, SavedObjectsFindResponsePublic, + CoreStart, } from 'kibana/public'; jest.mock('./index_pattern', () => { @@ -61,10 +61,10 @@ describe('IndexPatterns', () => { }) as Promise> ); - const uiSettings = {} as IUiSettingsClient; + const core = {} as CoreStart; const http = {} as HttpSetup; - indexPatterns = new IndexPatternsService(uiSettings, savedObjectsClient, http); + indexPatterns = new IndexPatternsService(core, savedObjectsClient, http); }); test('does cache gets for the same id', async () => { diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index acce5ed57683c..b5d66a6aab60a 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -22,11 +22,16 @@ import { SimpleSavedObject, IUiSettingsClient, HttpStart, + CoreStart, } from 'src/core/public'; import { createIndexPatternCache } from './_pattern_cache'; import { IndexPattern } from './index_pattern'; import { IndexPatternsApiClient, GetFieldsOptions } from './index_patterns_api_client'; +import { + createEnsureDefaultIndexPattern, + EnsureDefaultIndexPattern, +} from './ensure_default_index_pattern'; const indexPatternCache = createIndexPatternCache(); @@ -37,15 +42,13 @@ export class IndexPatternsService { private savedObjectsClient: SavedObjectsClientContract; private savedObjectsCache?: Array>> | null; private apiClient: IndexPatternsApiClient; + ensureDefaultIndexPattern: EnsureDefaultIndexPattern; - constructor( - config: IUiSettingsClient, - savedObjectsClient: SavedObjectsClientContract, - http: HttpStart - ) { + constructor(core: CoreStart, savedObjectsClient: SavedObjectsClientContract, http: HttpStart) { this.apiClient = new IndexPatternsApiClient(http); - this.config = config; + this.config = core.uiSettings; this.savedObjectsClient = savedObjectsClient; + this.ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(core); } private async refreshSavedObjectsCache() { diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index a2df754786a68..ba1df89c41358 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -57,6 +57,7 @@ const createStartContract = (): Start => { SearchBar: jest.fn(), }, indexPatterns: ({ + ensureDefaultIndexPattern: jest.fn(), make: () => ({ fieldsFetcher: { fetchForWildcard: jest.fn(), diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 924fcd6730f93..f3a88287313a0 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -160,7 +160,7 @@ export class DataPublicPlugin implements Plugin= 1) { - defaultId = patterns[0]; - core.uiSettings.set('defaultIndex', defaultId); - } else { - const canManageIndexPatterns = core.application.capabilities.management.kibana.index_patterns; - const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; - - if (timeoutId) { - clearTimeout(timeoutId); - } - - // Avoid being hostile to new users who don't have an index pattern setup yet - // give them a friendly info message instead of a terse error message - bannerId = core.overlays.banners.replace( - bannerId, - toMountPoint( - - ) - ); - - // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around - timeoutId = setTimeout(() => { - core.overlays.banners.remove(bannerId); - timeoutId = undefined; - }, 15000); - - history.push(redirectTarget); - - // return never-resolving promise to stop resolving and wait for the url change - return new Promise(() => {}); - } -} diff --git a/src/plugins/kibana_utils/public/history/index.ts b/src/plugins/kibana_utils/public/history/index.ts index 1a73bbb6b04a1..bb13ea09f928a 100644 --- a/src/plugins/kibana_utils/public/history/index.ts +++ b/src/plugins/kibana_utils/public/history/index.ts @@ -19,4 +19,3 @@ export { removeQueryParam } from './remove_query_param'; export { redirectWhenMissing } from './redirect_when_missing'; -export { ensureDefaultIndexPattern } from './ensure_default_index_pattern'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 2f139050e994a..c634322b23d0b 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -74,7 +74,7 @@ export { StartSyncStateFnType, StopSyncStateFnType, } from './state_sync'; -export { removeQueryParam, redirectWhenMissing, ensureDefaultIndexPattern } from './history'; +export { removeQueryParam, redirectWhenMissing } from './history'; export { applyDiff } from './state_management/utils/diff_object'; /** dummy plugin, we just want kibanaUtils to have its own bundle */ diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 563ce87b82ae5..fb3d6efa63826 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -238,6 +238,7 @@ exports[`SavedObjectsTable import should show the flyout 1`] = ` indexPatterns={ Object { "clearCache": [MockFunction], + "ensureDefaultIndexPattern": [MockFunction], "get": [MockFunction], "make": [Function], } diff --git a/src/plugins/visualize/public/application/legacy_app.js b/src/plugins/visualize/public/application/legacy_app.js index 7c5e3ce9408f0..c7cc11c1f3ff5 100644 --- a/src/plugins/visualize/public/application/legacy_app.js +++ b/src/plugins/visualize/public/application/legacy_app.js @@ -21,11 +21,7 @@ import { find } from 'lodash'; import { i18n } from '@kbn/i18n'; import { createHashHistory } from 'history'; -import { - createKbnUrlStateStorage, - redirectWhenMissing, - ensureDefaultIndexPattern, -} from '../../../kibana_utils/public'; +import { createKbnUrlStateStorage, redirectWhenMissing } from '../../../kibana_utils/public'; import { createSavedSearchesLoader } from '../../../discover/public'; import editorTemplate from './editor/editor.html'; @@ -127,7 +123,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => false, - hasDefaultIndex: history => ensureDefaultIndexPattern(deps.core, deps.data, history), + hasDefaultIndex: history => deps.data.indexPatterns.ensureDefaultIndexPattern(history), }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -138,7 +134,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => true, - hasDefaultIndex: history => ensureDefaultIndexPattern(deps.core, deps.data, history), + hasDefaultIndex: history => deps.data.indexPatterns.ensureDefaultIndexPattern(history), }, }) .when(VisualizeConstants.CREATE_PATH, { @@ -147,7 +143,7 @@ export function initVisualizeApp(app, deps) { k7Breadcrumbs: getCreateBreadcrumbs, resolve: { resolved: function($route, history) { - const { core, data, savedVisualizations, visualizations, toastNotifications } = deps; + const { data, savedVisualizations, visualizations, toastNotifications } = deps; const visTypes = visualizations.all(); const visType = find(visTypes, { name: $route.current.params.type }); const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; @@ -164,7 +160,8 @@ export function initVisualizeApp(app, deps) { ); } - return ensureDefaultIndexPattern(core, data, history) + return data.indexPatterns + .ensureDefaultIndexPattern(history) .then(() => savedVisualizations.get($route.current.params)) .then(getResolvedResults(deps)) .catch( @@ -183,9 +180,10 @@ export function initVisualizeApp(app, deps) { k7Breadcrumbs: getEditBreadcrumbs, resolve: { resolved: function($route, history) { - const { chrome, core, data, savedVisualizations, toastNotifications } = deps; + const { chrome, data, savedVisualizations, toastNotifications } = deps; - return ensureDefaultIndexPattern(core, data, history) + return data.indexPatterns + .ensureDefaultIndexPattern(history) .then(() => savedVisualizations.get($route.current.params.id)) .then(savedVis => { chrome.recentlyAccessed.add(savedVis.getFullPath(), savedVis.title, savedVis.id); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 909a736220d17..5b55c7a15f1b3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -671,6 +671,7 @@ "data.functions.esaggs.inspector.dataRequest.description": "このリクエストは Elasticsearch にクエリし、ビジュアライゼーション用のデータを取得します。", "data.functions.esaggs.inspector.dataRequest.title": "データ", "data.indexPatterns.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title} (ID: {id})", + "data.indexPatterns.ensureDefaultIndexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", "data.indexPatterns.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", "data.indexPatterns.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。", "data.indexPatterns.unknownFieldHeader": "不明なフィールドタイプ {type}", @@ -2428,7 +2429,6 @@ "kibana_legacy.paginate.size.allDropDownOptionLabel": "すべて", "kibana_utils.defaultFeedbackMessage": "フィードバックがありますか?{link} で問題を報告してください。", "kibana_utils.history.savedObjectIsMissingNotificationMessage": "保存されたオブジェクトがありません", - "kibana_utils.indexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", "kibana_utils.stateManagement.stateHash.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", "kibana_utils.stateManagement.stateHash.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", "kibana-react.dualRangeControl.mustSetBothErrorMessage": "下と上の値の両方を設定する必要があります", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0ebef9f8f1ef9..1758588d01ba8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -671,6 +671,7 @@ "data.functions.esaggs.inspector.dataRequest.description": "此请求将查询 Elasticsearch 以获取用于可视化的数据。", "data.functions.esaggs.inspector.dataRequest.title": "数据", "data.indexPatterns.fetchFieldErrorTitle": "提取索引模式 {title} (ID: {id}) 的字段时出错", + "data.indexPatterns.ensureDefaultIndexPattern.bannerLabel": "若要在 Kibana 中可视化和浏览数据,您需要创建索引模式,以从 Elasticsearch 检索数据。", "data.indexPatterns.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。", "data.indexPatterns.unknownFieldErrorMessage": "indexPattern “{title}” 中的字段 “{name}” 使用未知字段类型。", "data.indexPatterns.unknownFieldHeader": "未知字段类型 {type}", @@ -2429,7 +2430,6 @@ "kibana_legacy.paginate.size.allDropDownOptionLabel": "全部", "kibana_utils.defaultFeedbackMessage": "想反馈?请在 {link} 中创建问题。", "kibana_utils.history.savedObjectIsMissingNotificationMessage": "已保存对象缺失", - "kibana_utils.indexPattern.bannerLabel": "若要在 Kibana 中可视化和浏览数据,您需要创建索引模式,以从 Elasticsearch 检索数据。", "kibana_utils.stateManagement.stateHash.unableToRestoreUrlErrorMessage": "无法完全还原 URL,请确保使用共享功能。", "kibana_utils.stateManagement.stateHash.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,另外,似乎没有任何可安全删除的项目。\n\n通常,这可以通过移到全新的选项卡来解决,但这种情况可能是由更大的问题造成。如果您定期看到这个消息,请在 {gitHubIssuesUrl} 报告问题。", "kibana-react.dualRangeControl.mustSetBothErrorMessage": "下限值和上限值都须设置", From 2ace269a260829c7603f5925c304826363e000c8 Mon Sep 17 00:00:00 2001 From: Maryia Lapata Date: Fri, 24 Apr 2020 16:25:08 +0300 Subject: [PATCH 06/13] [NP] Vega migration (#63849) * Vega migartion * Move mocha tests to legacy * Fix TS * Update .i18nrc.json * Move mocks to vis_type_vega * Fix issue with babel and vega deps * Update mocha test * Mock services * Update vega_request_handler.ts * don't parse vega-lib/build/vega.js Co-authored-by: Elastic Machine Co-authored-by: spalger --- .i18nrc.json | 2 +- .../src/worker/webpack.config.ts | 5 +- .../__tests__/vis_type_vega}/vega_graph.hjson | 0 .../vis_type_vega}/vega_image_512.png | Bin .../vis_type_vega}/vega_map_image_256.png | Bin .../vis_type_vega}/vega_map_test.hjson | 0 .../vis_type_vega}/vega_tooltip_test.hjson | 0 .../vis_type_vega}/vega_visualization.js | 38 +++++++----- .../vis_type_vega}/vegalite_graph.hjson | 0 .../vis_type_vega}/vegalite_image_256.png | Bin .../vis_type_vega}/vegalite_image_512.png | Bin .../core_plugins/vis_type_vega/index.ts | 55 ------------------ .../core_plugins/vis_type_vega/package.json | 6 -- .../vis_type_vega/public/index.ts | 25 -------- .../vis_type_vega/public/legacy.ts | 38 ------------ .../ui/public/new_platform/new_platform.ts | 2 - .../ui/ui_exports/ui_export_defaults.js | 1 + src/plugins/vis_type_vega/kibana.json | 3 +- .../public/__mocks__/services.ts | 8 +-- .../vis_type_vega/public/_vega_editor.scss | 0 .../vis_type_vega/public/_vega_vis.scss | 0 .../vis_type_vega/public/components/index.ts | 0 .../public/components/vega_actions_menu.tsx | 0 .../public/components/vega_help_menu.tsx | 0 .../public/components/vega_vis_editor.tsx | 0 .../public/data_model/ems_file_parser.js | 0 .../public/data_model/es_query_parser.js | 0 .../public/data_model/es_query_parser.test.js | 0 .../public/data_model/search_cache.js | 0 .../public/data_model/search_cache.test.js | 0 .../public/data_model/time_cache.js | 0 .../public/data_model/time_cache.test.js | 0 .../public/data_model/url_parser.js | 0 .../vis_type_vega/public/data_model/utils.js | 0 .../public/data_model/vega_parser.js | 0 .../public/data_model/vega_parser.test.js | 0 .../vis_type_vega/public/default.spec.hjson | 0 .../vis_type_vega/public/index.scss | 2 - src/plugins/vis_type_vega/public/index.ts | 20 ++----- .../vis_type_vega/public/plugin.ts | 23 ++++---- .../vis_type_vega/public/services.ts | 7 +-- .../vis_type_vega/public/vega_fn.ts | 6 +- .../public/vega_request_handler.ts | 15 +++-- .../vis_type_vega/public/vega_type.ts | 4 +- .../public/vega_view/vega_base_view.js | 2 +- .../public/vega_view/vega_map_layer.js | 2 +- .../public/vega_view/vega_map_view.js | 2 +- .../public/vega_view/vega_tooltip.js | 0 .../public/vega_view/vega_view.js | 0 .../public/vega_visualization.js | 0 50 files changed, 72 insertions(+), 194 deletions(-) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vega_graph.hjson (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vega_image_512.png (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vega_map_image_256.png (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vega_map_test.hjson (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vega_tooltip_test.hjson (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vega_visualization.js (86%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vegalite_graph.hjson (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vegalite_image_256.png (100%) rename src/legacy/core_plugins/{vis_type_vega/public/__tests__ => kibana/public/__tests__/vis_type_vega}/vegalite_image_512.png (100%) delete mode 100644 src/legacy/core_plugins/vis_type_vega/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_vega/package.json delete mode 100644 src/legacy/core_plugins/vis_type_vega/public/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_vega/public/legacy.ts rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/__mocks__/services.ts (87%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/_vega_editor.scss (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/_vega_vis.scss (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/components/index.ts (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/components/vega_actions_menu.tsx (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/components/vega_help_menu.tsx (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/components/vega_vis_editor.tsx (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/ems_file_parser.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/es_query_parser.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/es_query_parser.test.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/search_cache.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/search_cache.test.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/time_cache.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/time_cache.test.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/url_parser.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/utils.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/vega_parser.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/data_model/vega_parser.test.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/default.spec.hjson (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/index.scss (78%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/plugin.ts (77%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/services.ts (86%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_fn.ts (94%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_request_handler.ts (84%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_type.ts (92%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_view/vega_base_view.js (99%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_view/vega_map_layer.js (94%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_view/vega_map_view.js (98%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_view/vega_tooltip.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_view/vega_view.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_vega/public/vega_visualization.js (100%) diff --git a/.i18nrc.json b/.i18nrc.json index 2ef92f544ad6b..35ce745234346 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -51,7 +51,7 @@ "visTypeTable": "src/plugins/vis_type_table", "visTypeTagCloud": "src/plugins/vis_type_tagcloud", "visTypeTimeseries": ["src/legacy/core_plugins/vis_type_timeseries", "src/plugins/vis_type_timeseries"], - "visTypeVega": "src/legacy/core_plugins/vis_type_vega", + "visTypeVega": "src/plugins/vis_type_vega", "visTypeVislib": "src/legacy/core_plugins/vis_type_vislib", "visTypeXy": "src/legacy/core_plugins/vis_type_xy", "visualizations": "src/plugins/visualizations", diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 3d52006fd2f35..7411e2df1b613 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -138,9 +138,12 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { module: { // no parse rules for a few known large packages which have no require() statements + // or which have require() statements that should be ignored because the file is + // already bundled with all its necessary depedencies noParse: [ /[\///]node_modules[\///]elasticsearch-browser[\///]/, - /[\///]node_modules[\///]lodash[\///]index\.js/, + /[\///]node_modules[\///]lodash[\///]index\.js$/, + /[\///]node_modules[\///]vega-lib[\///]build[\///]vega\.js$/, ], rules: [ diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_graph.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_graph.hjson rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_image_512.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_image_512.png similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_image_512.png rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_image_512.png diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_map_image_256.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_image_256.png similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_map_image_256.png rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_image_256.png diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_map_test.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_map_test.hjson rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_tooltip_test.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_tooltip_test.hjson rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js similarity index 86% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js index 6412d8a569b2a..21b7ea7dbf4c3 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js @@ -21,7 +21,9 @@ import Bluebird from 'bluebird'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import $ from 'jquery'; -import { createVegaVisualization } from '../vega_visualization'; +// Will be replaced with new path when tests are moved +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createVegaVisualization } from '../../../../../../plugins/vis_type_vega/public/vega_visualization'; import { ImageComparator } from 'test_utils/image_comparator'; import vegaliteGraph from '!!raw-loader!./vegalite_graph.hjson'; @@ -35,24 +37,34 @@ import vegaTooltipGraph from '!!raw-loader!./vega_tooltip_test.hjson'; import vegaMapGraph from '!!raw-loader!./vega_map_test.hjson'; import vegaMapImage256 from './vega_map_image_256.png'; +// Will be replaced with new path when tests are moved +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VegaParser } from '../../../../../../plugins/vis_type_vega/public/data_model/vega_parser'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { SearchCache } from '../../../../../../plugins/vis_type_vega/public/data_model/search_cache'; -import { VegaParser } from '../data_model/vega_parser'; -import { SearchCache } from '../data_model/search_cache'; - -import { createVegaTypeDefinition } from '../vega_type'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createVegaTypeDefinition } from '../../../../../../plugins/vis_type_vega/public/vega_type'; // TODO This is an integration test and thus requires a running platform. When moving to the new platform, // this test has to be migrated to the newly created integration test environment. // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { npStart } from 'ui/new_platform'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; +import { BaseVisType } from '../../../../../../plugins/visualizations/public/vis_types/base_vis_type'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; -import { setInjectedVars } from '../services'; +import { ExprVis } from '../../../../../../plugins/visualizations/public/expressions/vis'; + +import { + setInjectedVars, + setData, + setSavedObjects, + setNotifications, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/vis_type_vega/public/services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setInjectedVarFunc } from '../../../../../plugins/maps_legacy/public/kibana_services'; +import { setInjectedVarFunc } from '../../../../../../plugins/maps_legacy/public/kibana_services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ServiceSettings } from '../../../../../plugins/maps_legacy/public/map/service_settings'; +import { ServiceSettings } from '../../../../../../plugins/maps_legacy/public/map/service_settings'; const THRESHOLD = 0.1; const PIXEL_DIFF = 30; @@ -70,6 +82,9 @@ describe('VegaVisualizations', () => { enableExternalUrls: true, esShardTimeout: 10000, }); + setData(npStart.plugins.data); + setSavedObjects(npStart.core.savedObjects); + setNotifications(npStart.core.notifications); beforeEach(ngMock.module('kibana')); beforeEach( @@ -111,9 +126,6 @@ describe('VegaVisualizations', () => { timefilter: {}, }, }, - __LEGACY: { - esClient: npStart.plugins.data.search.__LEGACY.esClient, - }, }, }, }; diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_graph.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_graph.hjson rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_256.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_256.png similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_256.png rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_256.png diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_512.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_512.png similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_512.png rename to src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_512.png diff --git a/src/legacy/core_plugins/vis_type_vega/index.ts b/src/legacy/core_plugins/vis_type_vega/index.ts deleted file mode 100644 index ac7e407ca9e4d..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/index.ts +++ /dev/null @@ -1,55 +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 { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const vegaPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'vis_type_vega', - deprecations: ({ rename }: { rename: any }) => [ - rename('vega.enabled', 'vis_type_vega.enabled'), - ], - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - enableExternalUrls: Joi.boolean().default(false), - }).default(); - }, - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars: server => { - const serverConfig = server.config(); - const mapConfig: Record = serverConfig.get('map'); - - return { - emsTileLayerId: mapConfig.emsTileLayerId, - }; - }, - }, - init: (server: Legacy.Server) => ({}), - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default vegaPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_vega/package.json b/src/legacy/core_plugins/vis_type_vega/package.json deleted file mode 100644 index acd6f3da128ab..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "author": "Yuri Astrakhan", - "name": "vega", - "version": "kibana" -} - diff --git a/src/legacy/core_plugins/vis_type_vega/public/index.ts b/src/legacy/core_plugins/vis_type_vega/public/index.ts deleted file mode 100644 index 34ca0e72190e4..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/public/index.ts +++ /dev/null @@ -1,25 +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 { PluginInitializerContext } from '../../../../core/public'; -import { VegaPlugin as Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} diff --git a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts deleted file mode 100644 index 450af4a6f253e..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts +++ /dev/null @@ -1,38 +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 { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; -import { VegaPluginSetupDependencies, VegaPluginStartDependencies } from './plugin'; -import { plugin } from '.'; - -const setupPlugins: Readonly = { - ...npSetup.plugins, - visualizations: npSetup.plugins.visualizations, - mapsLegacy: npSetup.plugins.mapsLegacy, -}; - -const startPlugins: Readonly = { - ...npStart.plugins, -}; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 5ae2e2348aaa1..a15c7cce5511d 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -59,7 +59,6 @@ import { NavigationPublicPluginSetup, NavigationPublicPluginStart, } from '../../../../plugins/navigation/public'; -import { VisTypeVegaSetup } from '../../../../plugins/vis_type_vega/public'; import { DiscoverSetup, DiscoverStart } from '../../../../plugins/discover/public'; import { SavedObjectsManagementPluginSetup, @@ -88,7 +87,6 @@ export interface PluginsSetup { usageCollection: UsageCollectionSetup; advancedSettings: AdvancedSettingsSetup; management: ManagementSetup; - visTypeVega: VisTypeVegaSetup; discover: DiscoverSetup; visualizations: VisualizationsSetup; telemetry?: TelemetryPluginSetup; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index bb246d97bfe4e..35e1f8b7d2127 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -24,6 +24,7 @@ export const UI_EXPORT_DEFAULTS = { webpackNoParseRules: [ /node_modules[\/\\](angular|elasticsearch-browser)[\/\\]/, /node_modules[\/\\](mocha|moment)[\/\\]/, + /node_modules[\/\\]vega-lib[\/\\]build[\/\\]vega\.js$/, ], webpackAliases: { diff --git a/src/plugins/vis_type_vega/kibana.json b/src/plugins/vis_type_vega/kibana.json index 6bfd6c9536df4..f1f82e7f5b7ad 100644 --- a/src/plugins/vis_type_vega/kibana.json +++ b/src/plugins/vis_type_vega/kibana.json @@ -2,5 +2,6 @@ "id": "visTypeVega", "version": "kibana", "server": true, - "ui": true + "ui": true, + "requiredPlugins": ["data", "visualizations", "mapsLegacy", "expressions"] } diff --git a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts b/src/plugins/vis_type_vega/public/__mocks__/services.ts similarity index 87% rename from src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts rename to src/plugins/vis_type_vega/public/__mocks__/services.ts index b2f3e5b2241e6..1bf051232e4c9 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts +++ b/src/plugins/vis_type_vega/public/__mocks__/services.ts @@ -17,11 +17,11 @@ * under the License. */ -import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; -import { DataPublicPluginStart } from '../../../../../plugins/data/public'; +import { createGetterSetter } from '../../../kibana_utils/public'; +import { DataPublicPluginStart } from '../../../data/public'; import { IUiSettingsClient, NotificationsStart, SavedObjectsStart } from 'kibana/public'; -import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; -import { coreMock } from '../../../../../core/public/mocks'; +import { dataPluginMock } from '../../../data/public/mocks'; +import { coreMock } from '../../../../core/public/mocks'; export const [getData, setData] = createGetterSetter('Data'); setData(dataPluginMock.createStartContract()); diff --git a/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss b/src/plugins/vis_type_vega/public/_vega_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss rename to src/plugins/vis_type_vega/public/_vega_editor.scss diff --git a/src/legacy/core_plugins/vis_type_vega/public/_vega_vis.scss b/src/plugins/vis_type_vega/public/_vega_vis.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/_vega_vis.scss rename to src/plugins/vis_type_vega/public/_vega_vis.scss diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/index.ts b/src/plugins/vis_type_vega/public/components/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/components/index.ts rename to src/plugins/vis_type_vega/public/components/index.ts diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx b/src/plugins/vis_type_vega/public/components/vega_actions_menu.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx rename to src/plugins/vis_type_vega/public/components/vega_actions_menu.tsx diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_help_menu.tsx b/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/components/vega_help_menu.tsx rename to src/plugins/vis_type_vega/public/components/vega_help_menu.tsx diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx b/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx rename to src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/ems_file_parser.js b/src/plugins/vis_type_vega/public/data_model/ems_file_parser.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/ems_file_parser.js rename to src/plugins/vis_type_vega/public/data_model/ems_file_parser.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js b/src/plugins/vis_type_vega/public/data_model/es_query_parser.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js rename to src/plugins/vis_type_vega/public/data_model/es_query_parser.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js b/src/plugins/vis_type_vega/public/data_model/es_query_parser.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js rename to src/plugins/vis_type_vega/public/data_model/es_query_parser.test.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.js b/src/plugins/vis_type_vega/public/data_model/search_cache.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.js rename to src/plugins/vis_type_vega/public/data_model/search_cache.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js b/src/plugins/vis_type_vega/public/data_model/search_cache.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js rename to src/plugins/vis_type_vega/public/data_model/search_cache.test.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.js b/src/plugins/vis_type_vega/public/data_model/time_cache.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.js rename to src/plugins/vis_type_vega/public/data_model/time_cache.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js b/src/plugins/vis_type_vega/public/data_model/time_cache.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js rename to src/plugins/vis_type_vega/public/data_model/time_cache.test.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/url_parser.js b/src/plugins/vis_type_vega/public/data_model/url_parser.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/url_parser.js rename to src/plugins/vis_type_vega/public/data_model/url_parser.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/utils.js b/src/plugins/vis_type_vega/public/data_model/utils.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/utils.js rename to src/plugins/vis_type_vega/public/data_model/utils.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js b/src/plugins/vis_type_vega/public/data_model/vega_parser.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js rename to src/plugins/vis_type_vega/public/data_model/vega_parser.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js rename to src/plugins/vis_type_vega/public/data_model/vega_parser.test.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/default.spec.hjson b/src/plugins/vis_type_vega/public/default.spec.hjson similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/default.spec.hjson rename to src/plugins/vis_type_vega/public/default.spec.hjson diff --git a/src/legacy/core_plugins/vis_type_vega/public/index.scss b/src/plugins/vis_type_vega/public/index.scss similarity index 78% rename from src/legacy/core_plugins/vis_type_vega/public/index.scss rename to src/plugins/vis_type_vega/public/index.scss index 1ab2119d481a0..78d9eb61999f7 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/index.scss +++ b/src/plugins/vis_type_vega/public/index.scss @@ -1,5 +1,3 @@ -@import 'src/legacy/ui/public/styles/styling_constants'; - // Prefix all styles with "vga" to avoid conflicts. // Examples // vgaChart diff --git a/src/plugins/vis_type_vega/public/index.ts b/src/plugins/vis_type_vega/public/index.ts index 71f3474f8217e..78878d38e1674 100644 --- a/src/plugins/vis_type_vega/public/index.ts +++ b/src/plugins/vis_type_vega/public/index.ts @@ -19,20 +19,8 @@ import { PluginInitializerContext } from 'kibana/public'; import { ConfigSchema } from '../config'; +import { VegaPlugin as Plugin } from './plugin'; -export const plugin = (initializerContext: PluginInitializerContext) => ({ - setup() { - return { - /** - * The configuration is temporarily exposed to allow the legacy vega plugin to consume - * the setting. Once the vega plugin is migrated completely, this will become an implementation - * detail. - * @deprecated - */ - config: initializerContext.config.get(), - }; - }, - start() {}, -}); - -export type VisTypeVegaSetup = ReturnType['setup']>; +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts similarity index 77% rename from src/legacy/core_plugins/vis_type_vega/public/plugin.ts rename to src/plugins/vis_type_vega/public/plugin.ts index 9fa77d28fbbfa..c312705194cde 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/plugins/vis_type_vega/public/plugin.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { Plugin as DataPublicPlugin } from '../../data/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { setNotifications, setData, @@ -30,8 +30,10 @@ import { import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; -import { VisTypeVegaSetup } from '../../../../plugins/vis_type_vega/public'; -import { IServiceSettings } from '../../../../plugins/maps_legacy/public'; +import { IServiceSettings } from '../../maps_legacy/public'; +import { ConfigSchema } from '../config'; + +import './index.scss'; /** @internal */ export interface VegaVisualizationDependencies { @@ -47,7 +49,6 @@ export interface VegaPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; data: ReturnType; - visTypeVega: VisTypeVegaSetup; mapsLegacy: any; } @@ -58,18 +59,18 @@ export interface VegaPluginStartDependencies { /** @internal */ export class VegaPlugin implements Plugin, void> { - initializerContext: PluginInitializerContext; + initializerContext: PluginInitializerContext; - constructor(initializerContext: PluginInitializerContext) { + constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; } public async setup( core: CoreSetup, - { data, expressions, visualizations, visTypeVega, mapsLegacy }: VegaPluginSetupDependencies + { data, expressions, visualizations, mapsLegacy }: VegaPluginSetupDependencies ) { setInjectedVars({ - enableExternalUrls: visTypeVega.config.enableExternalUrls, + enableExternalUrls: this.initializerContext.config.get().enableExternalUrls, esShardTimeout: core.injectedMetadata.getInjectedVar('esShardTimeout') as number, emsTileLayerId: core.injectedMetadata.getInjectedVar('emsTileLayerId', true), }); diff --git a/src/legacy/core_plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_vega/public/services.ts rename to src/plugins/vis_type_vega/public/services.ts index 88e0e0098bf8c..e349cfbdc0024 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/services.ts +++ b/src/plugins/vis_type_vega/public/services.ts @@ -18,10 +18,9 @@ */ import { SavedObjectsStart } from 'kibana/public'; -import { NotificationsStart } from 'src/core/public'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; -import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -import { IUiSettingsClient } from '../../../../core/public'; +import { NotificationsStart, IUiSettingsClient } from 'src/core/public'; +import { DataPublicPluginStart } from '../../data/public'; +import { createGetterSetter } from '../../kibana_utils/public'; export const [getData, setData] = createGetterSetter('Data'); diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_fn.ts b/src/plugins/vis_type_vega/public/vega_fn.ts similarity index 94% rename from src/legacy/core_plugins/vis_type_vega/public/vega_fn.ts rename to src/plugins/vis_type_vega/public/vega_fn.ts index 2a0da81a31a96..6d45e043f7cee 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_fn.ts +++ b/src/plugins/vis_type_vega/public/vega_fn.ts @@ -19,11 +19,7 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - ExpressionFunctionDefinition, - KibanaContext, - Render, -} from '../../../../plugins/expressions/public'; +import { ExpressionFunctionDefinition, KibanaContext, Render } from '../../expressions/public'; import { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts b/src/plugins/vis_type_vega/public/vega_request_handler.ts similarity index 84% rename from src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts rename to src/plugins/vis_type_vega/public/vega_request_handler.ts index f63efc0007c3b..196e8fdcbafda 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts +++ b/src/plugins/vis_type_vega/public/vega_request_handler.ts @@ -17,9 +17,7 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getSearchService } from '../../../../plugins/data/public/services'; -import { Filter, esQuery, TimeRange, Query } from '../../../../plugins/data/public'; +import { Filter, esQuery, TimeRange, Query } from '../../data/public'; // @ts-ignore import { VegaParser } from './data_model/vega_parser'; @@ -30,6 +28,7 @@ import { TimeCache } from './data_model/time_cache'; import { VegaVisualizationDependencies } from './plugin'; import { VisParams } from './vega_fn'; +import { getData } from './services'; interface VegaRequestHandlerParams { query: Query; @@ -43,12 +42,18 @@ export function createVegaRequestHandler({ core: { uiSettings }, serviceSettings, }: VegaVisualizationDependencies) { - const { esClient } = getSearchService().__LEGACY; - const searchCache = new SearchCache(esClient, { max: 10, maxAge: 4 * 1000 }); + let searchCache: SearchCache | undefined; const { timefilter } = data.query.timefilter; const timeCache = new TimeCache(timefilter, 3 * 1000); return ({ timeRange, filters, query, visParams }: VegaRequestHandlerParams) => { + if (!searchCache) { + searchCache = new SearchCache(getData().search.__LEGACY.esClient, { + max: 10, + maxAge: 4 * 1000, + }); + } + timeCache.setTimeRange(timeRange); const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts similarity index 92% rename from src/legacy/core_plugins/vis_type_vega/public/vega_type.ts rename to src/plugins/vis_type_vega/public/vega_type.ts index f56d7682efc6f..c864553c118b9 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -18,10 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -import { DefaultEditorSize } from '../../../../plugins/vis_default_editor/public'; +import { DefaultEditorSize } from '../../vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; +import { defaultFeedbackMessage } from '../../kibana_utils/public'; import { createVegaRequestHandler } from './vega_request_handler'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js similarity index 99% rename from src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js rename to src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index c90f059ff7c94..be98d2b69ad87 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -26,7 +26,7 @@ import { Utils } from '../data_model/utils'; import { VISUALIZATION_COLORS } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TooltipHandler } from './vega_tooltip'; -import { esFilters } from '../../../../../plugins/data/public'; +import { esFilters } from '../../../data/public'; import { getEnableExternalUrls } from '../services'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js similarity index 94% rename from src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js rename to src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js index d43eb9c3351ea..8e4009eab8488 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js @@ -19,7 +19,7 @@ import L from 'leaflet'; import 'leaflet-vega'; -import { KibanaMapLayer } from '../../../../../plugins/maps_legacy/public'; +import { KibanaMapLayer } from '../../../maps_legacy/public'; export class VegaMapLayer extends KibanaMapLayer { constructor(spec, options) { diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js similarity index 98% rename from src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js rename to src/plugins/vis_type_vega/public/vega_view/vega_map_view.js index 03aef29dc5739..bd6652a597203 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js @@ -21,7 +21,7 @@ import * as vega from 'vega-lib'; import { i18n } from '@kbn/i18n'; import { VegaBaseView } from './vega_base_view'; import { VegaMapLayer } from './vega_map_layer'; -import { KibanaMap } from '../../../../../plugins/maps_legacy/public'; +import { KibanaMap } from '../../../maps_legacy/public'; import { getEmsTileLayerId, getUISettings } from '../services'; export class VegaMapView extends VegaBaseView { diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_tooltip.js b/src/plugins/vis_type_vega/public/vega_view/vega_tooltip.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_tooltip.js rename to src/plugins/vis_type_vega/public/vega_view/vega_tooltip.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_view.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_view.js rename to src/plugins/vis_type_vega/public/vega_view/vega_view.js diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/plugins/vis_type_vega/public/vega_visualization.js similarity index 100% rename from src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js rename to src/plugins/vis_type_vega/public/vega_visualization.js From fb70f01eff9fcb9ca8928b09fe27daff1cd386e7 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Fri, 24 Apr 2020 10:01:06 -0400 Subject: [PATCH 07/13] Task/hostlist pagination (#63722) * hostlist pagination for endpoint security --- .../endpoint/store/hosts/action.ts | 16 +- .../store/hosts/host_pagination.test.ts | 145 ++++++++++++++++++ .../endpoint/store/hosts/index.test.ts | 9 +- .../endpoint/store/hosts/middleware.test.ts | 17 +- .../endpoint/store/hosts/middleware.ts | 68 +++++--- .../endpoint/store/hosts/reducer.ts | 71 +++++++-- .../endpoint/store/hosts/selectors.ts | 56 ++++--- .../public/applications/endpoint/types.ts | 31 +++- .../view/hosts/details/host_details.tsx | 4 +- .../endpoint/view/hosts/details/index.tsx | 23 ++- .../applications/endpoint/view/hosts/hooks.ts | 4 +- .../endpoint/view/hosts/index.test.tsx | 16 +- .../endpoint/view/hosts/index.tsx | 118 +++++--------- .../feature_controls/endpoint_spaces.ts | 4 +- .../apps/endpoint/header_nav.ts | 4 +- 15 files changed, 417 insertions(+), 169 deletions(-) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 21871ec8ca849..56a49df3bdab4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostListPagination, ServerApiError } from '../../types'; +import { ServerApiError } from '../../types'; import { HostResultList, HostInfo } from '../../../../../common/types'; interface ServerReturnedHostList { @@ -12,6 +12,11 @@ interface ServerReturnedHostList { payload: HostResultList; } +interface ServerFailedToReturnHostList { + type: 'serverFailedToReturnHostList'; + payload: ServerApiError; +} + interface ServerReturnedHostDetails { type: 'serverReturnedHostDetails'; payload: HostInfo; @@ -22,13 +27,8 @@ interface ServerFailedToReturnHostDetails { payload: ServerApiError; } -interface UserPaginatedHostList { - type: 'userPaginatedHostList'; - payload: HostListPagination; -} - export type HostAction = | ServerReturnedHostList + | ServerFailedToReturnHostList | ServerReturnedHostDetails - | ServerFailedToReturnHostDetails - | UserPaginatedHostList; + | ServerFailedToReturnHostDetails; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts new file mode 100644 index 0000000000000..d2e1985d055c6 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, HttpSetup } from 'kibana/public'; +import { DepsStartMock, depsStartMock } from '../../mocks'; +import { AppAction, HostState, HostIndexUIQueryParams } from '../../types'; +import { Immutable, HostResultList } from '../../../../../common/types'; +import { History, createBrowserHistory } from 'history'; +import { hostMiddlewareFactory } from './middleware'; +import { applyMiddleware, Store, createStore } from 'redux'; +import { hostListReducer } from './reducer'; +import { coreMock } from 'src/core/public/mocks'; +import { urlFromQueryParams } from '../../view/hosts/url_from_query_params'; +import { uiQueryParams } from './selectors'; +import { mockHostResultList } from './mock_host_result_list'; +import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../test_utils'; + +describe('host list pagination: ', () => { + let fakeCoreStart: jest.Mocked; + let depsStart: DepsStartMock; + let fakeHttpServices: jest.Mocked; + let history: History; + let store: Store, Immutable>; + let queryParams: () => HostIndexUIQueryParams; + let waitForAction: MiddlewareActionSpyHelper['waitForAction']; + let actionSpyMiddleware; + const getEndpointListApiResponse = (): HostResultList => { + return mockHostResultList({ request_page_size: 1, request_page_index: 1, total: 10 }); + }; + + let historyPush: (params: HostIndexUIQueryParams) => void; + beforeEach(() => { + fakeCoreStart = coreMock.createStart(); + depsStart = depsStartMock(); + fakeHttpServices = fakeCoreStart.http as jest.Mocked; + history = createBrowserHistory(); + const middleware = hostMiddlewareFactory(fakeCoreStart, depsStart); + ({ actionSpyMiddleware, waitForAction } = createSpyMiddleware()); + store = createStore(hostListReducer, applyMiddleware(middleware, actionSpyMiddleware)); + + history.listen(location => { + store.dispatch({ type: 'userChangedUrl', payload: location }); + }); + + queryParams = () => uiQueryParams(store.getState()); + + historyPush = (nextQueryParams: HostIndexUIQueryParams): void => { + return history.push(urlFromQueryParams(nextQueryParams)); + }; + }); + + describe('when the user enteres the host list for the first time', () => { + it('the api is called with page_index and page_size defaulting to 0 and 10 respectively', async () => { + const apiResponse = getEndpointListApiResponse(); + fakeHttpServices.post.mockResolvedValue(apiResponse); + expect(fakeHttpServices.post).not.toHaveBeenCalled(); + + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/hosts', + }, + }); + await waitForAction('serverReturnedHostList'); + expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: '0' }, { page_size: '10' }], + }), + }); + }); + }); + describe('when a new page size is passed', () => { + it('should modify the url correctly', () => { + historyPush({ ...queryParams(), page_size: '20' }); + expect(queryParams()).toMatchInlineSnapshot(` + Object { + "page_index": "0", + "page_size": "20", + } + `); + }); + }); + describe('when an invalid page size is passed', () => { + it('should modify the page size in the url to the default page size', () => { + historyPush({ ...queryParams(), page_size: '1' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + }); + + describe('when a negative page size is passed', () => { + it('should modify the page size in the url to the default page size', () => { + historyPush({ ...queryParams(), page_size: '-1' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + }); + + describe('when a new page index is passed', () => { + it('should modify the page index in the url correctly', () => { + historyPush({ ...queryParams(), page_index: '2' }); + expect(queryParams()).toEqual({ page_index: '2', page_size: '10' }); + }); + }); + + describe('when a negative page index is passed', () => { + it('should modify the page index in the url to the default page index', () => { + historyPush({ ...queryParams(), page_index: '-2' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + }); + + describe('when invalid params are passed in the url', () => { + it('ignores non-numeric values for page_index and page_size', () => { + historyPush({ ...queryParams, page_index: 'one', page_size: 'fifty' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + + it('ignores unknown url search params', () => { + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/hosts', + search: '?foo=bar', + }, + }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + + it('ignores multiple values of the same query params except the last value', () => { + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/hosts', + search: '?page_index=2&page_index=3&page_size=20&page_size=50', + }, + }); + expect(queryParams()).toEqual({ page_index: '3', page_size: '50' }); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts index 6148934343635..515c54eab3280 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts @@ -6,12 +6,12 @@ import { createStore, Dispatch, Store } from 'redux'; import { HostAction, hostListReducer } from './index'; -import { HostListState } from '../../types'; +import { HostState } from '../../types'; import { listData } from './selectors'; import { mockHostResultList } from './mock_host_result_list'; describe('HostList store concerns', () => { - let store: Store; + let store: Store; let dispatch: Dispatch; const createTestStore = () => { store = createStore(hostListReducer); @@ -37,6 +37,11 @@ describe('HostList store concerns', () => { pageIndex: 0, total: 0, loading: false, + error: undefined, + details: undefined, + detailsLoading: false, + detailsError: undefined, + location: undefined, }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 8f39baddda00e..1af83a975d1d8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -9,21 +9,23 @@ import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; import { hostListReducer, hostMiddlewareFactory } from './index'; import { HostResultList, Immutable } from '../../../../../common/types'; -import { HostListState } from '../../types'; +import { HostState } from '../../types'; import { AppAction } from '../action'; import { listData } from './selectors'; import { DepsStartMock, depsStartMock } from '../../mocks'; import { mockHostResultList } from './mock_host_result_list'; +import { createSpyMiddleware, MiddlewareActionSpyHelper } from '../test_utils'; describe('host list middleware', () => { - const sleep = (ms = 100) => new Promise(wakeup => setTimeout(wakeup, ms)); let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; - type HostListStore = Store, Immutable>; + type HostListStore = Store, Immutable>; let store: HostListStore; let getState: HostListStore['getState']; let dispatch: HostListStore['dispatch']; + let waitForAction: MiddlewareActionSpyHelper['waitForAction']; + let actionSpyMiddleware; let history: History; const getEndpointListApiResponse = (): HostResultList => { @@ -33,15 +35,16 @@ describe('host list middleware', () => { fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); depsStart = depsStartMock(); fakeHttpServices = fakeCoreStart.http as jest.Mocked; + ({ actionSpyMiddleware, waitForAction } = createSpyMiddleware()); store = createStore( hostListReducer, - applyMiddleware(hostMiddlewareFactory(fakeCoreStart, depsStart)) + applyMiddleware(hostMiddlewareFactory(fakeCoreStart, depsStart), actionSpyMiddleware) ); getState = store.getState; dispatch = store.dispatch; history = createBrowserHistory(); }); - test('handles `userChangedUrl`', async () => { + it('handles `userChangedUrl`', async () => { const apiResponse = getEndpointListApiResponse(); fakeHttpServices.post.mockResolvedValue(apiResponse); expect(fakeHttpServices.post).not.toHaveBeenCalled(); @@ -53,10 +56,10 @@ describe('host list middleware', () => { pathname: '/hosts', }, }); - await sleep(); + await waitForAction('serverReturnedHostList'); expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { body: JSON.stringify({ - paging_properties: [{ page_index: 0 }, { page_size: 10 }], + paging_properties: [{ page_index: '0' }, { page_size: '10' }], }), }); expect(listData(getState())).toEqual(apiResponse.hosts.map(hostInfo => hostInfo.metadata)); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index 83e11f5408bcd..bb1cfc4dd10af 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -4,34 +4,64 @@ * you may not use this file except in compliance with the Elastic License. */ +import { HostResultList } from '../../../../../common/types'; +import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; +import { HostState } from '../../types'; import { ImmutableMiddlewareFactory } from '../../types'; -import { pageIndex, pageSize, isOnHostPage, hasSelectedHost, uiQueryParams } from './selectors'; -import { HostListState } from '../../types'; -export const hostMiddlewareFactory: ImmutableMiddlewareFactory = coreStart => { +export const hostMiddlewareFactory: ImmutableMiddlewareFactory = coreStart => { return ({ getState, dispatch }) => next => async action => { next(action); const state = getState(); if ( - (action.type === 'userChangedUrl' && - isOnHostPage(state) && - hasSelectedHost(state) !== true) || - action.type === 'userPaginatedHostList' + action.type === 'userChangedUrl' && + isOnHostPage(state) && + hasSelectedHost(state) !== true ) { - const hostPageIndex = pageIndex(state); - const hostPageSize = pageSize(state); - const response = await coreStart.http.post('/api/endpoint/metadata', { - body: JSON.stringify({ - paging_properties: [{ page_index: hostPageIndex }, { page_size: hostPageSize }], - }), - }); - response.request_page_index = hostPageIndex; - dispatch({ - type: 'serverReturnedHostList', - payload: response, - }); + const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); + try { + const response = await coreStart.http.post('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], + }), + }); + response.request_page_index = Number(pageIndex); + dispatch({ + type: 'serverReturnedHostList', + payload: response, + }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnHostList', + payload: error, + }); + } } if (action.type === 'userChangedUrl' && hasSelectedHost(state) !== false) { + // If user navigated directly to a host details page, load the host list + if (listData(state).length === 0) { + const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); + try { + const response = await coreStart.http.post('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], + }), + }); + response.request_page_index = Number(pageIndex); + dispatch({ + type: 'serverReturnedHostList', + payload: response, + }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnHostList', + payload: error, + }); + return; + } + } + + // call the host details api const { selected_host: selectedHost } = uiQueryParams(state); try { const response = await coreStart.http.get(`/api/endpoint/metadata/${selectedHost}`); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index 298e819645dbe..adf18fa50c24f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -4,23 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostListState, ImmutableReducer } from '../../types'; +import { Immutable } from '../../../../../common/types'; +import { HostState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; +import { isOnHostPage, hasSelectedHost } from './selectors'; -const initialState = (): HostListState => { +const initialState = (): HostState => { return { hosts: [], pageSize: 10, pageIndex: 0, total: 0, loading: false, - detailsError: undefined, + error: undefined, details: undefined, + detailsLoading: false, + detailsError: undefined, location: undefined, }; }; -export const hostListReducer: ImmutableReducer = ( +export const hostListReducer: ImmutableReducer = ( state = initialState(), action ) => { @@ -38,30 +42,77 @@ export const hostListReducer: ImmutableReducer = ( pageSize, pageIndex, loading: false, + error: undefined, + }; + } else if (action.type === 'serverFailedToReturnHostList') { + return { + ...state, + error: action.payload, + loading: false, }; } else if (action.type === 'serverReturnedHostDetails') { return { ...state, details: action.payload.metadata, + detailsLoading: false, + detailsError: undefined, }; } else if (action.type === 'serverFailedToReturnHostDetails') { return { ...state, detailsError: action.payload, + detailsLoading: false, }; - } else if (action.type === 'userPaginatedHostList') { - return { + } else if (action.type === 'userChangedUrl') { + const newState: Immutable = { ...state, - ...action.payload, - loading: true, + location: action.payload, }; - } else if (action.type === 'userChangedUrl') { + const isCurrentlyOnListPage = isOnHostPage(newState) && !hasSelectedHost(newState); + const wasPreviouslyOnListPage = isOnHostPage(state) && !hasSelectedHost(state); + const isCurrentlyOnDetailsPage = isOnHostPage(newState) && hasSelectedHost(newState); + const wasPreviouslyOnDetailsPage = isOnHostPage(state) && hasSelectedHost(state); + + // if on the host list page for the first time, return new location and load list + if (isCurrentlyOnListPage) { + if (!wasPreviouslyOnListPage) { + return { + ...state, + location: action.payload, + loading: true, + error: undefined, + detailsError: undefined, + }; + } + } else if (isCurrentlyOnDetailsPage) { + // if previous page was the list or another host details page, load host details only + if (wasPreviouslyOnDetailsPage || wasPreviouslyOnListPage) { + return { + ...state, + location: action.payload, + detailsLoading: true, + error: undefined, + detailsError: undefined, + }; + } else { + // if previous page was not host list or host details, load both list and details + return { + ...state, + location: action.payload, + loading: true, + detailsLoading: true, + error: undefined, + detailsError: undefined, + }; + } + } + // otherwise we are not on a host list or details page return { ...state, location: action.payload, + error: undefined, detailsError: undefined, }; } - return state; }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index 03cdba8505800..b0f949ebbe757 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -6,38 +6,47 @@ import querystring from 'querystring'; import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/types'; -import { HostListState, HostIndexUIQueryParams } from '../../types'; +import { HostState, HostIndexUIQueryParams } from '../../types'; -export const listData = (state: Immutable) => state.hosts; +const PAGE_SIZES = Object.freeze([10, 20, 50]); -export const pageIndex = (state: Immutable) => state.pageIndex; +export const listData = (state: Immutable) => state.hosts; -export const pageSize = (state: Immutable) => state.pageSize; +export const pageIndex = (state: Immutable): number => state.pageIndex; -export const totalHits = (state: Immutable) => state.total; +export const pageSize = (state: Immutable): number => state.pageSize; -export const isLoading = (state: Immutable) => state.loading; +export const totalHits = (state: Immutable): number => state.total; -export const detailsError = (state: Immutable) => state.detailsError; +export const listLoading = (state: Immutable): boolean => state.loading; -export const detailsData = (state: Immutable) => { - return state.details; -}; +export const listError = (state: Immutable) => state.error; -export const isOnHostPage = (state: Immutable) => +export const detailsData = (state: Immutable) => state.details; + +export const detailsLoading = (state: Immutable): boolean => state.detailsLoading; + +export const detailsError = (state: Immutable) => state.detailsError; + +export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; export const uiQueryParams: ( - state: Immutable + state: Immutable ) => Immutable = createSelector( - (state: Immutable) => state.location, - (location: Immutable['location']) => { - const data: HostIndexUIQueryParams = {}; + (state: Immutable) => state.location, + (location: Immutable['location']) => { + const data: HostIndexUIQueryParams = { page_index: '0', page_size: '10' }; if (location) { // Removes the `?` from the beginning of query string if it exists const query = querystring.parse(location.search.slice(1)); - const keys: Array = ['selected_host', 'show']; + const keys: Array = [ + 'selected_host', + 'page_size', + 'page_index', + 'show', + ]; for (const key of keys) { const value = query[key]; @@ -47,12 +56,23 @@ export const uiQueryParams: ( data[key] = value[value.length - 1]; } } + + // Check if page size is an expected size, otherwise default to 10 + if (!PAGE_SIZES.includes(Number(data.page_size))) { + data.page_size = '10'; + } + + // Check if page index is a valid positive integer, otherwise default to 0 + const pageIndexAsNumber = Number(data.page_index); + if (!Number.isFinite(pageIndexAsNumber) || pageIndexAsNumber < 0) { + data.page_index = '0'; + } } return data; } ); -export const hasSelectedHost: (state: Immutable) => boolean = createSelector( +export const hasSelectedHost: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_host: selectedHost }) => { return selectedHost !== undefined; @@ -60,7 +80,7 @@ export const hasSelectedHost: (state: Immutable) => boolean = cre ); /** What policy details panel view to show */ -export const showView: (state: HostListState) => 'policy_response' | 'details' = createSelector( +export const showView: (state: HostState) => 'policy_response' | 'details' = createSelector( uiQueryParams, searchParams => { return searchParams.show === 'policy_response' ? 'policy_response' : 'details'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index f407d32cb3b42..e5e600f6c6288 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -88,23 +88,40 @@ export type SubstateMiddlewareFactory = ( middleware: ImmutableMiddleware ) => Middleware<{}, GlobalState, Dispatch>>; -export interface HostListState { +export interface HostState { + /** list of host **/ hosts: HostMetadata[]; + /** number of items per page */ pageSize: number; + /** which page to show */ pageIndex: number; + /** total number of hosts returned */ total: number; + /** list page is retrieving data */ loading: boolean; - detailsError?: ServerApiError; + /** api error from retrieving host list */ + error?: ServerApiError; + /** details data for a specific host */ details?: Immutable; + /** details page is retrieving data */ + detailsLoading: boolean; + /** api error from retrieving host details */ + detailsError?: ServerApiError; + /** current location info */ location?: Immutable; } -export interface HostListPagination { - pageIndex: number; - pageSize: number; -} +/** + * Query params on the host page parsed from the URL + */ export interface HostIndexUIQueryParams { + /** Selected host id shows host details flyout */ selected_host?: string; + /** How many items to show in list */ + page_size?: string; + /** Which page to show */ + page_index?: string; + /** show the policy response or host details */ show?: string; } @@ -257,7 +274,7 @@ export type KeysByValueCriteria = { export type MalwareProtectionOSes = KeysByValueCriteria; export interface GlobalState { - readonly hostList: HostListState; + readonly hostList: HostState; readonly alertList: AlertListState; readonly policyList: PolicyListState; readonly policyDetails: PolicyDetailsState; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx index 336308b2ee271..e4da39d50304a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { HostMetadata } from '../../../../../../common/types'; import { FormattedDateAndTime } from '../../formatted_date_time'; import { LinkToApp } from '../../components/link_to_app'; -import { useHostListSelector, useHostLogsUrl } from '../hooks'; +import { useHostSelector, useHostLogsUrl } from '../hooks'; import { urlFromQueryParams } from '../url_from_query_params'; import { uiQueryParams } from '../../../store/hosts/selectors'; import { useNavigateByRouterEventHandler } from '../../hooks/use_navigate_by_router_event_handler'; @@ -33,7 +33,7 @@ const HostIds = styled(EuiListGroupItem)` export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { appId, appPath, url } = useHostLogsUrl(details.host.id); - const queryParams = useHostListSelector(uiQueryParams); + const queryParams = useHostSelector(uiQueryParams); const detailsResultsUpper = useMemo(() => { return [ { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx index 0c43e18822508..e44a45f300daa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx @@ -17,9 +17,15 @@ import { useHistory } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { useHostListSelector } from '../hooks'; +import { useHostSelector } from '../hooks'; import { urlFromQueryParams } from '../url_from_query_params'; -import { uiQueryParams, detailsData, detailsError, showView } from '../../../store/hosts/selectors'; +import { + uiQueryParams, + detailsData, + detailsError, + showView, + detailsLoading, +} from '../../../store/hosts/selectors'; import { HostDetails } from './host_details'; import { PolicyResponse } from './policy_response'; import { HostMetadata } from '../../../../../../common/types'; @@ -29,11 +35,12 @@ import { useNavigateByRouterEventHandler } from '../../hooks/use_navigate_by_rou export const HostDetailsFlyout = memo(() => { const history = useHistory(); const { notifications } = useKibana(); - const queryParams = useHostListSelector(uiQueryParams); + const queryParams = useHostSelector(uiQueryParams); const { selected_host: selectedHost, ...queryParamsWithoutSelectedHost } = queryParams; - const details = useHostListSelector(detailsData); - const error = useHostListSelector(detailsError); - const show = useHostListSelector(showView); + const details = useHostSelector(detailsData); + const loading = useHostSelector(detailsLoading); + const error = useHostSelector(detailsError); + const show = useHostSelector(showView); const handleFlyoutClose = useCallback(() => { history.push(urlFromQueryParams(queryParamsWithoutSelectedHost)); @@ -64,7 +71,7 @@ export const HostDetailsFlyout = memo(() => {

- {details === undefined ? : details.host.hostname} + {loading ? : details?.host?.hostname}

@@ -93,7 +100,7 @@ export const HostDetailsFlyout = memo(() => { const PolicyResponseFlyoutPanel = memo<{ hostMeta: HostMetadata; }>(({ hostMeta }) => { - const { show, ...queryParams } = useHostListSelector(uiQueryParams); + const { show, ...queryParams } = useHostSelector(uiQueryParams); const detailsUri = useMemo( () => urlFromQueryParams({ diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts index 7eb51f3a7b294..eb242f5c535f4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts @@ -6,10 +6,10 @@ import { useSelector } from 'react-redux'; import { useMemo } from 'react'; -import { GlobalState, HostListState } from '../../types'; +import { GlobalState, HostState } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -export function useHostListSelector(selector: (state: HostListState) => TSelected) { +export function useHostSelector(selector: (state: HostState) => TSelected) { return useSelector(function(state: GlobalState) { return selector(state.hostList); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index 88416b577ed0c..11dbed716c527 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -139,7 +139,7 @@ describe('when on the hosts page', () => { expect(policyStatusLink).not.toBeNull(); expect(policyStatusLink.textContent).toEqual('Successful'); expect(policyStatusLink.getAttribute('href')).toEqual( - '?selected_host=1&show=policy_response' + '?page_index=0&page_size=10&selected_host=1&show=policy_response' ); }); it('should update the URL when policy status link is clicked', async () => { @@ -150,7 +150,9 @@ describe('when on the hosts page', () => { fireEvent.click(policyStatusLink); }); const changedUrlAction = await userChangedUrlChecker; - expect(changedUrlAction.payload.search).toEqual('?selected_host=1&show=policy_response'); + expect(changedUrlAction.payload.search).toEqual( + '?page_index=0&page_size=10&selected_host=1&show=policy_response' + ); }); it('should include the link to logs', async () => { const renderResult = render(); @@ -170,7 +172,7 @@ describe('when on the hosts page', () => { }); }); - it('should navigate to logs without full page refresh', async () => { + it('should navigate to logs without full page refresh', () => { expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); }); }); @@ -205,7 +207,9 @@ describe('when on the hosts page', () => { it('should include the back to details link', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); expect(subHeaderBackLink.textContent).toBe('Endpoint Details'); - expect(subHeaderBackLink.getAttribute('href')).toBe('?selected_host=1'); + expect(subHeaderBackLink.getAttribute('href')).toBe( + '?page_index=0&page_size=10&selected_host=1' + ); }); it('should update URL when back to details link is clicked', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); @@ -214,7 +218,9 @@ describe('when on the hosts page', () => { fireEvent.click(subHeaderBackLink); }); const changedUrlAction = await userChangedUrlChecker; - expect(changedUrlAction.payload.search).toEqual('?selected_host=1'); + expect(changedUrlAction.payload.search).toEqual( + '?page_index=0&page_size=10&selected_host=1' + ); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index e662bafed6492..5c2922162ce0c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -5,31 +5,19 @@ */ import React, { useMemo, useCallback, memo } from 'react'; -import { useDispatch } from 'react-redux'; -import { - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiPageContent, - EuiHorizontalRule, - EuiTitle, - EuiBasicTable, - EuiText, - EuiLink, - EuiHealth, -} from '@elastic/eui'; +import { EuiHorizontalRule, EuiBasicTable, EuiText, EuiLink, EuiHealth } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; import { EuiBasicTableColumn } from '@elastic/eui'; import { HostDetailsFlyout } from './details'; import * as selectors from '../../store/hosts/selectors'; -import { HostAction } from '../../store/hosts/action'; -import { useHostListSelector } from './hooks'; +import { useHostSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; import { HostMetadata, Immutable } from '../../../../../common/types'; +import { PageView } from '../components/page_view'; import { useNavigateByRouterEventHandler } from '../hooks/use_navigate_by_router_event_handler'; const HostLink = memo<{ @@ -49,16 +37,17 @@ const HostLink = memo<{ const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { - const dispatch = useDispatch<(a: HostAction) => void>(); + const history = useHistory(); const { listData, pageIndex, pageSize, totalHits: totalItemCount, - isLoading, + listLoading: loading, + listError, uiQueryParams: queryParams, hasSelectedHost, - } = useHostListSelector(selector); + } = useHostSelector(selector); const paginationSetup = useMemo(() => { return { @@ -73,12 +62,15 @@ export const HostList = () => { const onTableChange = useCallback( ({ page }: { page: { index: number; size: number } }) => { const { index, size } = page; - dispatch({ - type: 'userPaginatedHostList', - payload: { pageIndex: index, pageSize: size }, - }); + history.push( + urlFromQueryParams({ + ...queryParams, + page_index: JSON.stringify(index), + page_size: JSON.stringify(size), + }) + ); }, - [dispatch] + [history, queryParams] ); const columns: Array>> = useMemo(() => { @@ -100,6 +92,7 @@ export const HostList = () => { name: i18n.translate('xpack.endpoint.host.list.policy', { defaultMessage: 'Policy', }), + truncateText: true, render: () => { return 'Policy Name'; }, @@ -134,6 +127,7 @@ export const HostList = () => { name: i18n.translate('xpack.endpoint.host.list.ip', { defaultMessage: 'IP Address', }), + truncateText: true, }, { field: '', @@ -158,59 +152,29 @@ export const HostList = () => { }, [queryParams]); return ( - + {hasSelectedHost && } - - - - -

- -

-
-
- - - - - - - [...listData], [listData])} - columns={columns} - loading={isLoading} - pagination={paginationSetup} - onChange={onTableChange} - /> - -
-
-
+ + + + + [...listData], [listData])} + columns={columns} + loading={loading} + error={listError?.message} + pagination={paginationSetup} + onChange={onTableChange} + /> + ); }; - -const HostPage = styled.div` - .hostPage { - padding: 0; - } - .hostHeader { - background-color: ${props => props.theme.eui.euiColorLightestShade}; - border-bottom: ${props => props.theme.eui.euiBorderThin}; - padding: ${props => - props.theme.eui.euiSizeXL + - ' ' + - 0 + - props.theme.eui.euiSizeXL + - ' ' + - props.theme.eui.euiSizeL}; - margin-bottom: 0; - } - .hostPageContent { - border: none; - } -`; diff --git a/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts b/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts index c543046031e9f..fdebdae9e5d0e 100644 --- a/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts @@ -41,13 +41,13 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('welcomeTitle'); }); - it(`endpoint management shows 'Hosts'`, async () => { + it(`endpoint hosts shows hosts lists page`, async () => { await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts', undefined, { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); - await testSubjects.existOrFail('hostListTitle'); + await testSubjects.existOrFail('hostPage'); }); }); diff --git a/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts b/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts index c2c4068212484..b944056e00911 100644 --- a/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts @@ -31,7 +31,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the hosts page when the Hosts tab is selected', async () => { await (await testSubjects.find('hostsEndpointTab')).click(); - await testSubjects.existOrFail('hostListTitle'); + await testSubjects.existOrFail('hostPage'); }); it('renders the alerts page when the Alerts tab is selected', async () => { @@ -46,7 +46,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the home page when Home tab is selected after selecting another tab', async () => { await (await testSubjects.find('hostsEndpointTab')).click(); - await testSubjects.existOrFail('hostListTitle'); + await testSubjects.existOrFail('hostPage'); await (await testSubjects.find('homeEndpointTab')).click(); await testSubjects.existOrFail('welcomeTitle'); From 103a3cd11c9e1fa9a3d9e4267c9ae0bc19761315 Mon Sep 17 00:00:00 2001 From: Gil Raphaelli Date: Fri, 24 Apr 2020 10:29:20 -0400 Subject: [PATCH 08/13] update apm index pattern (#64232) --- x-pack/plugins/apm/server/tutorial/index_pattern.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/server/tutorial/index_pattern.json b/x-pack/plugins/apm/server/tutorial/index_pattern.json index ede9ba54681c0..e0d9595585b88 100644 --- a/x-pack/plugins/apm/server/tutorial/index_pattern.json +++ b/x-pack/plugins/apm/server/tutorial/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, From dfddcdd903e6e86f9bf2770e7cd8badd2ea3c19f Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Fri, 24 Apr 2020 10:39:54 -0400 Subject: [PATCH 09/13] [Canvas] Misc NP Stuff (#63703) * Timelion function -> np * embeddable renderer -> np i18n context * ui_metric -> np * Fix timelion issue Co-authored-by: Elastic Machine --- .../renderers/embeddable/embeddable.tsx | 3 +- .../canvas/i18n/functions/dict/timelion.ts | 4 +- .../plugins/canvas/public/application.tsx | 5 + .../plugins/canvas/public/functions/index.ts | 15 +- .../canvas/public/functions/timelion.ts | 173 +++++++++--------- x-pack/legacy/plugins/canvas/public/legacy.ts | 2 + .../plugins/canvas/public/lib/ui_metric.ts | 21 ++- .../legacy/plugins/canvas/public/plugin.tsx | 15 +- 8 files changed, 141 insertions(+), 97 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx index a1096d50c1653..ee08dfb87e1c1 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx @@ -6,7 +6,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; import { CoreStart } from '../../../../../../../src/core/public'; import { StartDeps } from '../../plugin'; import { @@ -30,6 +29,8 @@ const embeddablesRegistry: { } = {}; const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => { + const I18nContext = core.i18n.Context; + return (embeddableObject: IEmbeddable, domNode: HTMLElement) => { return (
> = { +export const help: FunctionHelp>> = { help: i18n.translate('xpack.canvas.functions.timelionHelpText', { defaultMessage: 'Use Timelion to extract one or more timeseries from many sources.', }), diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index f75b3b427c41b..7ebfdda743a97 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -30,6 +30,7 @@ import { VALUE_CLICK_TRIGGER, ActionByType } from '../../../../../src/plugins/ui /* eslint-disable */ import { ACTION_VALUE_CLICK } from '../../../../../src/plugins/data/public/actions/value_click_action'; /* eslint-enable */ +import { init as initStatsReporter } from './lib/ui_metric'; import { CapabilitiesStrings } from '../i18n'; const { ReadOnlyBadge: strings } = CapabilitiesStrings; @@ -121,6 +122,10 @@ export const initializeCanvas = async ( startPlugins.uiActions.attachAction(VALUE_CLICK_TRIGGER, emptyAction); } + if (setupPlugins.usageCollection) { + initStatsReporter(setupPlugins.usageCollection.reportUiStats); + } + return canvasStore; }; diff --git a/x-pack/legacy/plugins/canvas/public/functions/index.ts b/x-pack/legacy/plugins/canvas/public/functions/index.ts index 27fb7d83274a4..5e098d8f175c5 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/index.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/index.ts @@ -4,16 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionsSetup } from 'src/plugins/expressions/public'; import { asset } from './asset'; import { filtersFunctionFactory } from './filters'; -import { timelion } from './timelion'; +import { timelionFunctionFactory } from './timelion'; import { toFunctionFactory } from './to'; +import { CanvasSetupDeps, CoreSetup } from '../plugin'; export interface InitializeArguments { - typesRegistry: ExpressionsSetup['__LEGACY']['types']; + prependBasePath: CoreSetup['http']['basePath']['prepend']; + typesRegistry: CanvasSetupDeps['expressions']['__LEGACY']['types']; + timefilter: CanvasSetupDeps['data']['query']['timefilter']['timefilter']; } export function initFunctions(initialize: InitializeArguments) { - return [asset, filtersFunctionFactory(initialize), timelion, toFunctionFactory(initialize)]; + return [ + asset, + filtersFunctionFactory(initialize), + timelionFunctionFactory(initialize), + toFunctionFactory(initialize), + ]; } diff --git a/x-pack/legacy/plugins/canvas/public/functions/timelion.ts b/x-pack/legacy/plugins/canvas/public/functions/timelion.ts index ae87e858cf796..e59d798108945 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/timelion.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/timelion.ts @@ -6,8 +6,6 @@ import { flatten } from 'lodash'; import moment from 'moment-timezone'; -import chrome from 'ui/chrome'; -import { npStart } from 'ui/new_platform'; import { TimeRange } from 'src/plugins/data/common'; import { ExpressionFunctionDefinition, DatatableRow } from 'src/plugins/expressions/public'; import { fetch } from '../../common/lib/fetch'; @@ -15,6 +13,7 @@ import { fetch } from '../../common/lib/fetch'; import { buildBoolArray } from '../../server/lib/build_bool_array'; import { Datatable, Filter } from '../../types'; import { getFunctionHelp } from '../../i18n'; +import { InitializeArguments } from './'; interface Arguments { query: string; @@ -30,13 +29,17 @@ interface Arguments { * @param timeRange time range to parse * @param timeZone time zone to do the parsing in */ -function parseDateMath(timeRange: TimeRange, timeZone: string) { +function parseDateMath( + timeRange: TimeRange, + timeZone: string, + timefilter: InitializeArguments['timefilter'] +) { // the datemath plugin always parses dates by using the current default moment time zone. // to use the configured time zone, we are switching just for the bounds calculation. const defaultTimezone = moment().zoneName(); moment.tz.setDefault(timeZone); - const parsedRange = npStart.plugins.data.query.timefilter.timefilter.calculateBounds(timeRange); + const parsedRange = timefilter.calculateBounds(timeRange); // reset default moment timezone moment.tz.setDefault(defaultTimezone); @@ -44,96 +47,100 @@ function parseDateMath(timeRange: TimeRange, timeZone: string) { return parsedRange; } -export function timelion(): ExpressionFunctionDefinition< +type TimelionFunction = ExpressionFunctionDefinition< 'timelion', Filter, Arguments, Promise -> { - const { help, args: argHelp } = getFunctionHelp().timelion; +>; - return { - name: 'timelion', - type: 'datatable', - inputTypes: ['filter'], - help, - args: { - query: { - types: ['string'], - aliases: ['_', 'q'], - help: argHelp.query, - default: '".es(*)"', - }, - interval: { - types: ['string'], - help: argHelp.interval, - default: 'auto', - }, - from: { - types: ['string'], - help: argHelp.from, - default: 'now-1y', - }, - to: { - types: ['string'], - help: argHelp.to, - default: 'now', - }, - timezone: { - types: ['string'], - help: argHelp.timezone, - default: 'UTC', +export function timelionFunctionFactory(initialize: InitializeArguments): () => TimelionFunction { + return () => { + const { help, args: argHelp } = getFunctionHelp().timelion; + + return { + name: 'timelion', + type: 'datatable', + inputTypes: ['filter'], + help, + args: { + query: { + types: ['string'], + aliases: ['_', 'q'], + help: argHelp.query, + default: '".es(*)"', + }, + interval: { + types: ['string'], + help: argHelp.interval, + default: 'auto', + }, + from: { + types: ['string'], + help: argHelp.from, + default: 'now-1y', + }, + to: { + types: ['string'], + help: argHelp.to, + default: 'now', + }, + timezone: { + types: ['string'], + help: argHelp.timezone, + default: 'UTC', + }, }, - }, - fn: (input, args): Promise => { - // Timelion requires a time range. Use the time range from the timefilter element in the - // workpad, if it exists. Otherwise fall back on the function args. - const timeFilter = input.and.find(and => and.type === 'time'); - const range = timeFilter - ? { min: timeFilter.from, max: timeFilter.to } - : parseDateMath({ from: args.from, to: args.to }, args.timezone); + fn: (input, args): Promise => { + // Timelion requires a time range. Use the time range from the timefilter element in the + // workpad, if it exists. Otherwise fall back on the function args. + const timeFilter = input.and.find(and => and.type === 'time'); + const range = timeFilter + ? { min: timeFilter.from, max: timeFilter.to } + : parseDateMath({ from: args.from, to: args.to }, args.timezone, initialize.timefilter); - const body = { - extended: { - es: { - filter: { - bool: { - must: buildBoolArray(input.and), + const body = { + extended: { + es: { + filter: { + bool: { + must: buildBoolArray(input.and), + }, }, }, }, - }, - sheet: [args.query], - time: { - from: range.min, - to: range.max, - interval: args.interval, - timezone: args.timezone, - }, - }; + sheet: [args.query], + time: { + from: range.min, + to: range.max, + interval: args.interval, + timezone: args.timezone, + }, + }; - return fetch(chrome.addBasePath(`/api/timelion/run`), { - method: 'POST', - responseType: 'json', - data: body, - }).then(resp => { - const seriesList = resp.data.sheet[0].list; - const rows = flatten( - seriesList.map((series: { data: any[]; label: string }) => - series.data.map(row => ({ '@timestamp': row[0], value: row[1], label: series.label })) - ) - ) as DatatableRow[]; + return fetch(initialize.prependBasePath(`/api/timelion/run`), { + method: 'POST', + responseType: 'json', + data: body, + }).then(resp => { + const seriesList = resp.data.sheet[0].list; + const rows = flatten( + seriesList.map((series: { data: any[]; label: string }) => + series.data.map(row => ({ '@timestamp': row[0], value: row[1], label: series.label })) + ) + ) as DatatableRow[]; - return { - type: 'datatable', - columns: [ - { name: '@timestamp', type: 'date' }, - { name: 'value', type: 'number' }, - { name: 'label', type: 'string' }, - ], - rows, - }; - }); - }, + return { + type: 'datatable', + columns: [ + { name: '@timestamp', type: 'date' }, + { name: 'value', type: 'number' }, + { name: 'label', type: 'string' }, + ], + rows, + }; + }); + }, + }; }; } diff --git a/x-pack/legacy/plugins/canvas/public/legacy.ts b/x-pack/legacy/plugins/canvas/public/legacy.ts index 4af7c9b2bd057..5bb628909c32e 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy.ts @@ -21,8 +21,10 @@ const shimCoreStart = { }; const shimSetupPlugins: CanvasSetupDeps = { + data: npSetup.plugins.data, expressions: npSetup.plugins.expressions, home: npSetup.plugins.home, + usageCollection: npSetup.plugins.usageCollection, }; const shimStartPlugins: CanvasStartDeps = { ...npStart.plugins, diff --git a/x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts b/x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts index 33976a147df46..2a1a4b88b7264 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts @@ -4,10 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - createUiStatsReporter, - METRIC_TYPE, -} from '../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { UiStatsMetricType, METRIC_TYPE } from '@kbn/analytics'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -export const trackCanvasUiMetric = createUiStatsReporter('canvas'); export { METRIC_TYPE }; + +export let reportUiStats: UsageCollectionSetup['reportUiStats'] | undefined; + +export function init(_reportUiStats: UsageCollectionSetup['reportUiStats']): void { + reportUiStats = _reportUiStats; +} + +export function trackCanvasUiMetric(metricType: UiStatsMetricType, name: string | string[]) { + if (!reportUiStats) { + return; + } + + reportUiStats('canvas', metricType, name); +} diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index 3ea3ce625ca71..36ce1974be272 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -10,8 +10,10 @@ import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { initLoadingIndicator } from './lib/loading_indicator'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; +import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public'; import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; // @ts-ignore untyped local import { argTypeSpecs } from './expression_types/arg_types'; @@ -20,7 +22,7 @@ import { legacyRegistries } from './legacy_plugin_support'; import { getPluginApi, CanvasApi } from './plugin_api'; import { initFunctions } from './functions'; import { CanvasSrcPlugin } from '../canvas_plugin_src/plugin'; -export { CoreStart }; +export { CoreStart, CoreSetup }; /** * These are the private interfaces for the services your plugin depends on. @@ -28,14 +30,17 @@ export { CoreStart }; */ // This interface will be built out as we require other plugins for setup export interface CanvasSetupDeps { + data: DataPublicPluginSetup; expressions: ExpressionsSetup; home: HomePublicPluginSetup; + usageCollection?: UsageCollectionSetup; } export interface CanvasStartDeps { embeddable: EmbeddableStart; expressions: ExpressionsStart; inspector: InspectorStart; + uiActions: UiActionsStart; __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; @@ -94,7 +99,13 @@ export class CanvasPlugin canvasApi.addTypes(legacyRegistries.types.getOriginalFns()); // Register core canvas stuff - canvasApi.addFunctions(initFunctions({ typesRegistry: plugins.expressions.__LEGACY.types })); + canvasApi.addFunctions( + initFunctions({ + timefilter: plugins.data.query.timefilter.timefilter, + prependBasePath: core.http.basePath.prepend, + typesRegistry: plugins.expressions.__LEGACY.types, + }) + ); canvasApi.addArgumentUIs(argTypeSpecs); canvasApi.addTransitions(transitions); From 4051c945684830cda62ebdc2f5c70ebd4387c5ef Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Fri, 24 Apr 2020 10:53:27 -0400 Subject: [PATCH 10/13] [CANVAS] Moves notify to a canvas service (#63268) * Moves notify to a canvas service * Typecheck fix --- .../plugins/canvas/public/application.tsx | 14 +++- .../canvas/public/apps/workpad/routes.js | 10 ++- .../asset_manager/asset_manager.tsx | 2 +- .../asset_manager/{index.js => index.ts} | 47 ++++++++---- .../public/components/render_with_fn/index.js | 9 ++- .../components/saved_elements_modal/index.ts | 19 +++-- .../workpad_header/share_menu/flyout/index.ts | 7 +- .../workpad_header/share_menu/index.ts | 12 +-- .../workpad_header/view_menu/index.ts | 2 - .../public/components/workpad_loader/index.js | 26 ++++--- .../workpad_loader/upload_workpad.js | 3 +- .../workpad_loader/workpad_dropzone/index.js | 5 +- .../workpad_loader/workpad_loader.js | 8 +- .../components/workpad_templates/index.js | 11 ++- x-pack/legacy/plugins/canvas/public/index.ts | 3 +- .../canvas/public/lib/download_workpad.ts | 17 +++-- .../public/lib/element_handler_creators.ts | 26 +++---- .../plugins/canvas/public/lib/es_service.ts | 15 ++-- .../plugins/canvas/public/lib/notify.js | 52 ------------- .../canvas/public/lib/run_interpreter.ts | 5 +- .../plugins/canvas/public/services/index.ts | 73 +++++++++++++++++++ .../plugins/canvas/public/services/notify.ts | 57 +++++++++++++++ .../canvas/public/state/actions/elements.js | 8 +- .../public/state/middleware/es_persist.js | 8 +- 24 files changed, 287 insertions(+), 152 deletions(-) rename x-pack/legacy/plugins/canvas/public/components/asset_manager/{index.js => index.ts} (63%) delete mode 100644 x-pack/legacy/plugins/canvas/public/lib/notify.js create mode 100644 x-pack/legacy/plugins/canvas/public/services/index.ts create mode 100644 x-pack/legacy/plugins/canvas/public/services/notify.ts diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index 7ebfdda743a97..f746a24e9b261 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -33,6 +33,9 @@ import { ACTION_VALUE_CLICK } from '../../../../../src/plugins/data/public/actio import { init as initStatsReporter } from './lib/ui_metric'; import { CapabilitiesStrings } from '../i18n'; + +import { startServices, stopServices, services } from './services'; + const { ReadOnlyBadge: strings } = CapabilitiesStrings; let restoreAction: ActionByType | undefined; @@ -51,8 +54,14 @@ export const renderApp = ( { element }: AppMountParameters, canvasStore: Store ) => { + const canvasServices = Object.entries(services).reduce((reduction, [key, provider]) => { + reduction[key] = provider.getService(); + + return reduction; + }, {} as Record); + ReactDOM.render( - + @@ -71,6 +80,8 @@ export const initializeCanvas = async ( startPlugins: CanvasStartDeps, registries: SetupRegistries ) => { + startServices(coreSetup, coreStart, setupPlugins, startPlugins); + // Create Store const canvasStore = await createStore(coreSetup, setupPlugins); @@ -130,6 +141,7 @@ export const initializeCanvas = async ( }; export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDeps) => { + stopServices(); destroyRegistries(); resetInterpreter(); diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js b/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js index 718443fcdd990..4e3920bf34f67 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js @@ -6,7 +6,7 @@ import { ErrorStrings } from '../../../i18n'; import * as workpadService from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; +import { notifyService } from '../../services'; import { getBaseBreadcrumb, getWorkpadBreadcrumb, setBreadcrumb } from '../../lib/breadcrumbs'; import { getDefaultWorkpad } from '../../state/defaults'; import { setWorkpad } from '../../state/actions/workpad'; @@ -33,7 +33,9 @@ export const routes = [ dispatch(resetAssets()); router.redirectTo('loadWorkpad', { id: newWorkpad.id, page: 1 }); } catch (err) { - notify.error(err, { title: strings.getCreateFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getCreateFailureErrorMessage() }); router.redirectTo('home'); } }, @@ -59,7 +61,9 @@ export const routes = [ // reset transient properties when changing workpads dispatch(setZoomScale(1)); } catch (err) { - notify.error(err, { title: strings.getLoadFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getLoadFailureErrorMessage() }); return router.redirectTo('home'); } } diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx index 479e9287d7adf..c27f0c002c3d1 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx @@ -15,7 +15,7 @@ import { AssetModal } from './asset_modal'; const { AssetManager: strings } = ComponentStrings; -interface Props { +export interface Props { /** A list of assets, if available */ assetValues: AssetType[]; /** Function to invoke when an asset is selected to be added as an element to the workpad */ diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.js b/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.ts similarity index 63% rename from x-pack/legacy/plugins/canvas/public/components/asset_manager/index.js rename to x-pack/legacy/plugins/canvas/public/components/asset_manager/index.ts index 6c05eec0c3c09..3fd34d6d2a9bb 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.ts @@ -8,29 +8,36 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { set, get } from 'lodash'; import { fromExpression, toExpression } from '@kbn/interpreter/common'; -import { notify } from '../../lib/notify'; import { getAssets } from '../../state/selectors/assets'; +// @ts-ignore Untyped local import { removeAsset, createAsset } from '../../state/actions/assets'; +// @ts-ignore Untyped local import { elementsRegistry } from '../../lib/elements_registry'; +// @ts-ignore Untyped local import { addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; import { encode } from '../../../common/lib/dataurl'; import { getId } from '../../lib/get_id'; +// @ts-ignore Untyped Local import { findExistingAsset } from '../../lib/find_existing_asset'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; -import { AssetManager as Component } from './asset_manager'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { WithKibanaProps } from '../../'; +import { AssetManager as Component, Props as AssetManagerProps } from './asset_manager'; -const mapStateToProps = state => ({ +import { State, ExpressionAstExpression, AssetType } from '../../../types'; + +const mapStateToProps = (state: State) => ({ assets: getAssets(state), selectedPage: getSelectedPage(state), }); -const mapDispatchToProps = dispatch => ({ - onAddImageElement: pageId => assetId => { +const mapDispatchToProps = (dispatch: (action: any) => void) => ({ + onAddImageElement: (pageId: string) => (assetId: string) => { const imageElement = elementsRegistry.get('image'); const elementAST = fromExpression(imageElement.expression); const selector = ['chain', '0', 'arguments', 'dataurl']; - const subExp = [ + const subExp: ExpressionAstExpression[] = [ { type: 'expression', chain: [ @@ -44,11 +51,11 @@ const mapDispatchToProps = dispatch => ({ ], }, ]; - const newAST = set(elementAST, selector, subExp); + const newAST = set(elementAST, selector, subExp); imageElement.expression = toExpression(newAST); dispatch(addElement(pageId, imageElement)); }, - onAssetAdd: (type, content) => { + onAssetAdd: (type: string, content: string) => { // make the ID here and pass it into the action const assetId = getId('asset'); dispatch(createAsset(type, content, assetId)); @@ -56,10 +63,14 @@ const mapDispatchToProps = dispatch => ({ // then return the id, so the caller knows the id that will be created return assetId; }, - onAssetDelete: assetId => dispatch(removeAsset(assetId)), + onAssetDelete: (assetId: string) => dispatch(removeAsset(assetId)), }); -const mergeProps = (stateProps, dispatchProps, ownProps) => { +const mergeProps = ( + stateProps: ReturnType, + dispatchProps: ReturnType, + ownProps: AssetManagerProps +) => { const { assets, selectedPage } = stateProps; const { onAssetAdd } = dispatchProps; const assetValues = Object.values(assets); // pull values out of assets object @@ -70,16 +81,16 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { onAddImageElement: dispatchProps.onAddImageElement(stateProps.selectedPage), selectedPage, assetValues, - onAssetAdd: file => { + onAssetAdd: (file: File) => { const [type, subtype] = get(file, 'type', '').split('/'); if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { return encode(file).then(dataurl => { - const type = 'dataurl'; - const existingId = findExistingAsset(type, dataurl, assetValues); + const dataurlType = 'dataurl'; + const existingId = findExistingAsset(dataurlType, dataurl, assetValues); if (existingId) { return existingId; } - return onAssetAdd(type, dataurl); + return onAssetAdd(dataurlType, dataurl); }); } @@ -88,7 +99,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { }; }; -export const AssetManager = compose( +export const AssetManager = compose( connect(mapStateToProps, mapDispatchToProps, mergeProps), - withProps({ onAssetCopy: asset => notify.success(`Copied '${asset.id}' to clipboard`) }) + withKibana, + withProps(({ kibana }: WithKibanaProps) => ({ + onAssetCopy: (asset: AssetType) => + kibana.services.canvas.notify.success(`Copied '${asset.id}' to clipboard`), + })) )(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js b/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js index 68c3ba79dd488..cc234d2287c0c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js @@ -7,7 +7,7 @@ import { compose, withProps, withPropsOnChange } from 'recompose'; import PropTypes from 'prop-types'; import isEqual from 'react-fast-compare'; -import { notify } from '../../lib/notify'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { RenderWithFn as Component } from './render_with_fn'; import { ElementHandlers } from './lib/handlers'; @@ -19,9 +19,10 @@ export const RenderWithFn = compose( handlers: Object.assign(new ElementHandlers(), handlers), }) ), - withProps({ - onError: notify.error, - }) + withKibana, + withProps(props => ({ + onError: props.kibana.services.canvas.notify.error, + })) )(Component); RenderWithFn.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts index bb088ad4e0de1..60d1d7462daa9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts @@ -11,8 +11,8 @@ import { camelCase } from 'lodash'; // @ts-ignore Untyped local import { cloneSubgraphs } from '../../lib/clone_subgraphs'; import * as customElementService from '../../lib/custom_element_service'; -// @ts-ignore Untyped local -import { notify } from '../../lib/notify'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { WithKibanaProps } from '../../'; // @ts-ignore Untyped local import { selectToplevelNodes } from '../../state/actions/transient'; // @ts-ignore Untyped local @@ -64,7 +64,7 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ const mergeProps = ( stateProps: StateProps, dispatchProps: DispatchProps, - ownProps: OwnPropsWithState + ownProps: OwnPropsWithState & WithKibanaProps ): ComponentProps => { const { pageId } = stateProps; const { onClose, search, setCustomElements } = ownProps; @@ -92,7 +92,9 @@ const mergeProps = ( try { await findCustomElements(); } catch (err) { - notify.error(err, { title: `Couldn't find custom elements` }); + ownProps.kibana.services.canvas.notify.error(err, { + title: `Couldn't find custom elements`, + }); } }, // remove custom element @@ -101,7 +103,9 @@ const mergeProps = ( await customElementService.remove(id); await findCustomElements(); } catch (err) { - notify.error(err, { title: `Couldn't delete custom elements` }); + ownProps.kibana.services.canvas.notify.error(err, { + title: `Couldn't delete custom elements`, + }); } }, // update custom element @@ -115,13 +119,16 @@ const mergeProps = ( }); await findCustomElements(); } catch (err) { - notify.error(err, { title: `Couldn't update custom elements` }); + ownProps.kibana.services.canvas.notify.error(err, { + title: `Couldn't update custom elements`, + }); } }, }; }; export const SavedElementsModal = compose( + withKibana, withState('search', 'setSearch', ''), withState('customElements', 'setCustomElements', []), connect(mapStateToProps, mapDispatchToProps, mergeProps) diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts index 6ab419656a7ee..4377635acac88 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts @@ -12,7 +12,6 @@ import { getRenderedWorkpadExpressions, } from '../../../../state/selectors/workpad'; // @ts-ignore Untyped local -import { notify } from '../../../../lib/notify'; import { downloadRenderedWorkpad, downloadRuntime, @@ -70,7 +69,7 @@ export const ShareWebsiteFlyout = compose unsupportedRenderers, onClose, onCopy: () => { - notify.info(strings.getCopyShareConfigMessage()); + kibana.services.canvas.notify.info(strings.getCopyShareConfigMessage()); }, onDownload: type => { switch (type) { @@ -86,7 +85,9 @@ export const ShareWebsiteFlyout = compose .post(`${basePath}${API_ROUTE_SHAREABLE_ZIP}`, JSON.stringify(renderedWorkpad)) .then(blob => downloadZippedRuntime(blob.data)) .catch((err: Error) => { - notify.error(err, { title: strings.getShareableZipErrorTitle(workpad.name) }); + kibana.services.canvas.notify.error(err, { + title: strings.getShareableZipErrorTitle(workpad.name), + }); }); return; default: diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts index 6b51e5d999e8b..d6565f0e43db7 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts @@ -8,8 +8,6 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { jobCompletionNotifications } from '../../../../../../../plugins/reporting/public'; import { getWorkpad, getPages } from '../../../state/selectors/workpad'; -// @ts-ignore Untyped local -import { notify } from '../../../lib/notify'; import { getWindow } from '../../../lib/get_window'; import { downloadWorkpad } from '../../../lib/download_workpad'; import { ShareMenu as Component, Props as ComponentProps } from './share_menu'; @@ -59,10 +57,10 @@ export const ShareMenu = compose( onCopy: type => { switch (type) { case 'pdf': - notify.info(strings.getCopyPDFMessage()); + kibana.services.canvas.notify.info(strings.getCopyPDFMessage()); break; case 'reportingConfig': - notify.info(strings.getCopyReportingConfigMessage()); + kibana.services.canvas.notify.info(strings.getCopyReportingConfigMessage()); break; default: throw new Error(strings.getUnknownExportErrorMessage(type)); @@ -73,7 +71,7 @@ export const ShareMenu = compose( case 'pdf': return createPdf(workpad, { pageCount }, kibana.services.http.basePath) .then(({ data }: { data: { job: { id: string } } }) => { - notify.info(strings.getExportPDFMessage(), { + kibana.services.canvas.notify.info(strings.getExportPDFMessage(), { title: strings.getExportPDFTitle(workpad.name), }); @@ -81,7 +79,9 @@ export const ShareMenu = compose( jobCompletionNotifications.add(data.job.id); }) .catch((err: Error) => { - notify.error(err, { title: strings.getExportPDFErrorTitle(workpad.name) }); + kibana.services.canvas.notify.error(err, { + title: strings.getExportPDFErrorTitle(workpad.name), + }); }); case 'json': downloadWorkpad(workpad.id); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts index c5aa8278ecf55..eee613183639c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts @@ -9,8 +9,6 @@ import { compose, withHandlers } from 'recompose'; import { Dispatch } from 'redux'; import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/'; import { zoomHandlerCreators } from '../../../lib/app_handler_creators'; -// @ts-ignore Untyped local -import { notify } from '../../../lib/notify'; import { State, CanvasWorkpadBoundingBox } from '../../../../types'; // @ts-ignore Untyped local import { fetchAllRenderables } from '../../../state/actions/elements'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js index 226ad420535bd..9379379e54d97 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js @@ -9,7 +9,6 @@ import { connect } from 'react-redux'; import { compose, withState, getContext, withHandlers, withProps } from 'recompose'; import moment from 'moment'; import * as workpadService from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; import { canUserWrite } from '../../state/selectors/app'; import { getWorkpad } from '../../state/selectors/workpad'; import { getId } from '../../lib/get_id'; @@ -32,7 +31,11 @@ export const WorkpadLoader = compose( }), connect(mapStateToProps), withState('workpads', 'setWorkpads', null), - withHandlers({ + withKibana, + withProps(({ kibana }) => ({ + notify: kibana.services.canvas.notify, + })), + withHandlers(({ kibana }) => ({ // Workpad creation via navigation createWorkpad: props => async workpad => { // workpad data uploaded, create and load it @@ -41,7 +44,9 @@ export const WorkpadLoader = compose( await workpadService.create(workpad); props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }); } catch (err) { - notify.error(err, { title: errors.getUploadFailureErrorMessage() }); + kibana.services.canvas.notify.error(err, { + title: errors.getUploadFailureErrorMessage(), + }); } return; } @@ -55,7 +60,7 @@ export const WorkpadLoader = compose( const workpads = await workpadService.find(text); setWorkpads(workpads); } catch (err) { - notify.error(err, { title: errors.getFindFailureErrorMessage() }); + kibana.services.canvas.notify.error(err, { title: errors.getFindFailureErrorMessage() }); } }, @@ -71,7 +76,7 @@ export const WorkpadLoader = compose( await workpadService.create(workpad); props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }); } catch (err) { - notify.error(err, { title: errors.getCloneFailureErrorMessage() }); + kibana.services.canvas.notify.error(err, { title: errors.getCloneFailureErrorMessage() }); } }, @@ -92,7 +97,7 @@ export const WorkpadLoader = compose( return Promise.all(removeWorkpads).then(results => { let redirectHome = false; - const [passes, errors] = results.reduce( + const [passes, errored] = results.reduce( ([passes, errors], result) => { if (result.id === loadedWorkpad && !result.err) { redirectHome = true; @@ -116,8 +121,8 @@ export const WorkpadLoader = compose( workpads: remainingWorkpads, }; - if (errors.length > 0) { - notify.error(errors.getDeleteFailureErrorMessage()); + if (errored.length > 0) { + kibana.services.canvas.notify.error(errors.getDeleteFailureErrorMessage()); } setWorkpads(workpadState); @@ -126,11 +131,10 @@ export const WorkpadLoader = compose( props.router.navigateTo('home'); } - return errors.map(({ id }) => id); + return errored.map(({ id }) => id); }); }, - }), - withKibana, + })), withProps(props => ({ formatDate: date => { const dateFormat = props.kibana.services.uiSettings.get('dateFormat'); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js index a7fcf7449ce40..fd25fb03a9ca9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js @@ -6,12 +6,11 @@ import { get } from 'lodash'; import { getId } from '../../lib/get_id'; -import { notify } from '../../lib/notify'; import { ErrorStrings } from '../../../i18n'; const { WorkpadFileUpload: errors } = ErrorStrings; -export const uploadWorkpad = (file, onUpload) => { +export const uploadWorkpad = (file, onUpload, notify) => { if (!file) { return; } diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js index ac716d37f532d..ab0c064d5ef07 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js @@ -6,7 +6,6 @@ import PropTypes from 'prop-types'; import { compose, withHandlers } from 'recompose'; -import { notify } from '../../../lib/notify'; import { uploadWorkpad } from '../upload_workpad'; import { ErrorStrings } from '../../../../i18n'; import { WorkpadDropzone as Component } from './workpad_dropzone'; @@ -14,7 +13,7 @@ import { WorkpadDropzone as Component } from './workpad_dropzone'; const { WorkpadFileUpload: errors } = ErrorStrings; export const WorkpadDropzone = compose( - withHandlers({ + withHandlers(({ notify }) => ({ onDropAccepted: ({ onUpload }) => ([file]) => uploadWorkpad(file, onUpload), onDropRejected: () => ([file]) => { notify.warning(errors.getAcceptJSONOnlyErrorMessage(), { @@ -23,7 +22,7 @@ export const WorkpadDropzone = compose( : errors.getFileUploadFailureWithoutFileNameErrorMessage(), }); }, - }) + })) )(Component); WorkpadDropzone.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 04378e5603c4b..cb5af27144c7f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -249,7 +249,11 @@ export class WorkpadLoader extends React.PureComponent { return ( - + uploadWorkpad(file, this.onUpload)} + onChange={([file]) => uploadWorkpad(file, this.onUpload, this.props.notify)} accept="application/json" disabled={createPending || !canUserWrite} /> diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js index 139d0f283bf1a..1890ca1f9d2d6 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js @@ -7,9 +7,9 @@ import PropTypes from 'prop-types'; import { compose, getContext, withHandlers, withProps } from 'recompose'; import * as workpadService from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; import { getId } from '../../lib/get_id'; import { templatesRegistry } from '../../lib/templates_registry'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { WorkpadTemplates as Component } from './workpad_templates'; export const WorkpadTemplates = compose( @@ -19,7 +19,8 @@ export const WorkpadTemplates = compose( withProps(() => ({ templates: templatesRegistry.toJS(), })), - withHandlers({ + withKibana, + withHandlers(({ kibana }) => ({ // Clone workpad given an id cloneWorkpad: props => workpad => { workpad.id = getId('workpad'); @@ -31,7 +32,9 @@ export const WorkpadTemplates = compose( return workpadService .create(workpad) .then(() => props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 })) - .catch(err => notify.error(err, { title: `Couldn't clone workpad template` })); + .catch(err => + kibana.services.canvas.notify.error(err, { title: `Couldn't clone workpad template` }) + ); }, - }) + })) )(Component); diff --git a/x-pack/legacy/plugins/canvas/public/index.ts b/x-pack/legacy/plugins/canvas/public/index.ts index b8358bfe022e6..b053920fec6e4 100644 --- a/x-pack/legacy/plugins/canvas/public/index.ts +++ b/x-pack/legacy/plugins/canvas/public/index.ts @@ -10,6 +10,7 @@ import { CoreStart, } from '../../../../../src/core/public'; import { CanvasSetup, CanvasStart, CanvasSetupDeps, CanvasStartDeps, CanvasPlugin } from './plugin'; +import { CanvasServices } from './services'; export const plugin: PluginInitializer< CanvasSetup, @@ -22,7 +23,7 @@ export const plugin: PluginInitializer< export interface WithKibanaProps { kibana: { - services: CoreStart & CanvasStartDeps; + services: CoreStart & CanvasStartDeps & { canvas: CanvasServices }; }; } diff --git a/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts b/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts index e4866641fd9e1..fb038d8b6ace2 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts @@ -6,8 +6,7 @@ import fileSaver from 'file-saver'; import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../common/lib/constants'; import { ErrorStrings } from '../../i18n'; -// @ts-ignore untyped local -import { notify } from './notify'; +import { notifyService } from '../services'; // @ts-ignore untyped local import * as workpadService from './workpad_service'; import { CanvasRenderedWorkpad } from '../../shareable_runtime/types'; @@ -20,7 +19,7 @@ export const downloadWorkpad = async (workpadId: string) => { const jsonBlob = new Blob([JSON.stringify(workpad)], { type: 'application/json' }); fileSaver.saveAs(jsonBlob, `canvas-workpad-${workpad.name}-${workpad.id}.json`); } catch (err) { - notify.error(err, { title: strings.getDownloadFailureErrorMessage() }); + notifyService.getService().error(err, { title: strings.getDownloadFailureErrorMessage() }); } }; @@ -32,7 +31,9 @@ export const downloadRenderedWorkpad = async (renderedWorkpad: CanvasRenderedWor `canvas-embed-workpad-${renderedWorkpad.name}-${renderedWorkpad.id}.json` ); } catch (err) { - notify.error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() }); } }; @@ -42,7 +43,9 @@ export const downloadRuntime = async (basePath: string) => { window.open(path); return; } catch (err) { - notify.error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() }); } }; @@ -51,6 +54,8 @@ export const downloadZippedRuntime = async (data: any) => { const zip = new Blob([data], { type: 'octet/stream' }); fileSaver.saveAs(zip, 'canvas-workpad-embed.zip'); } catch (err) { - notify.error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() }); } }; diff --git a/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts index bce6bc51b366c..a8744b4820842 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Http2ServerResponse } from 'http2'; import { camelCase } from 'lodash'; // @ts-ignore unconverted local file import { getClipboardData, setClipboardData } from './clipboard'; // @ts-ignore unconverted local file import { cloneSubgraphs } from './clone_subgraphs'; -// @ts-ignore unconverted local file -import { notify } from './notify'; +import { notifyService } from '../services'; import * as customElementService from './custom_element_service'; import { getId } from './get_id'; import { PositionedElement } from '../../types'; @@ -86,15 +84,17 @@ export const basicHandlerCreators = { customElementService .create(customElement) .then(() => - notify.success( - `Custom element '${customElement.displayName || customElement.id}' was saved`, - { - 'data-test-subj': 'canvasCustomElementCreate-success', - } - ) + notifyService + .getService() + .success( + `Custom element '${customElement.displayName || customElement.id}' was saved`, + { + 'data-test-subj': 'canvasCustomElementCreate-success', + } + ) ) - .catch((result: Http2ServerResponse) => - notify.warning(result, { + .catch((error: Error) => + notifyService.getService().warning(error, { title: `Custom element '${customElement.displayName || customElement.id}' was not saved`, }) @@ -138,13 +138,13 @@ export const clipboardHandlerCreators = { if (selectedNodes.length) { setClipboardData({ selectedNodes }); removeNodes(selectedNodes.map(extractId), pageId); - notify.success('Cut element to clipboard'); + notifyService.getService().success('Cut element to clipboard'); } }, copyNodes: ({ selectedNodes }: Props) => (): void => { if (selectedNodes.length) { setClipboardData({ selectedNodes }); - notify.success('Copied element to clipboard'); + notifyService.getService().success('Copied element to clipboard'); } }, pasteNodes: ({ insertNodes, pageId, selectToplevelNodes }: Props) => (): void => { diff --git a/x-pack/legacy/plugins/canvas/public/lib/es_service.ts b/x-pack/legacy/plugins/canvas/public/lib/es_service.ts index 32f4fe041423c..6aa4968f29155 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/es_service.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/es_service.ts @@ -10,8 +10,7 @@ import { API_ROUTE } from '../../common/lib/constants'; // @ts-ignore untyped local import { fetch } from '../../common/lib/fetch'; import { ErrorStrings } from '../../i18n'; -// @ts-ignore untyped local -import { notify } from './notify'; +import { notifyService } from '../services'; import { getCoreStart } from '../legacy'; const { esService: strings } = ErrorStrings; @@ -38,7 +37,7 @@ export const getFields = (index = '_all') => { .sort() ) .catch((err: Error) => - notify.error(err, { + notifyService.getService().error(err, { title: strings.getFieldsFetchErrorMessage(index), }) ); @@ -57,7 +56,9 @@ export const getIndices = () => return savedObject.attributes.title; }); }) - .catch((err: Error) => notify.error(err, { title: strings.getIndicesFetchErrorMessage() })); + .catch((err: Error) => + notifyService.getService().error(err, { title: strings.getIndicesFetchErrorMessage() }) + ); export const getDefaultIndex = () => { const defaultIndexId = getAdvancedSettings().get('defaultIndex'); @@ -66,6 +67,10 @@ export const getDefaultIndex = () => { ? getSavedObjectsClient() .get('index-pattern', defaultIndexId) .then(defaultIndex => defaultIndex.attributes.title) - .catch(err => notify.error(err, { title: strings.getDefaultIndexFetchErrorMessage() })) + .catch(err => + notifyService + .getService() + .error(err, { title: strings.getDefaultIndexFetchErrorMessage() }) + ) : Promise.resolve(''); }; diff --git a/x-pack/legacy/plugins/canvas/public/lib/notify.js b/x-pack/legacy/plugins/canvas/public/lib/notify.js deleted file mode 100644 index 64876a02a3c64..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/lib/notify.js +++ /dev/null @@ -1,52 +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 { get } from 'lodash'; -import { getCoreStart, getStartPlugins } from '../legacy'; - -const getToastNotifications = function() { - return getCoreStart().notifications.toasts; -}; - -const formatMsg = function(...args) { - return getStartPlugins().__LEGACY.formatMsg(...args); -}; - -const getToast = (err, opts = {}) => { - const errData = get(err, 'response') || err; - const errMsg = formatMsg(errData); - const { title, ...rest } = opts; - let text = null; - - if (title) { - text = errMsg; - } - - return { - ...rest, - title: title || errMsg, - text, - }; -}; - -export const notify = { - /* - * @param {(string | Object)} err: message or Error object - * @param {Object} opts: option to override toast title or icon, see https://github.com/elastic/kibana/blob/master/src/legacy/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md - */ - error(err, opts) { - getToastNotifications().addDanger(getToast(err, opts)); - }, - warning(err, opts) { - getToastNotifications().addWarning(getToast(err, opts)); - }, - info(err, opts) { - getToastNotifications().add(getToast(err, opts)); - }, - success(err, opts) { - getToastNotifications().addSuccess(getToast(err, opts)); - }, -}; diff --git a/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts index fbbaf0ccf280e..df338f40e08d9 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts @@ -6,8 +6,7 @@ import { fromExpression, getType } from '@kbn/interpreter/common'; import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public'; -// @ts-ignore Untyped Local -import { notify } from './notify'; +import { notifyService } from '../services'; import { CanvasStartDeps, CanvasSetupDeps } from '../plugin'; @@ -85,7 +84,7 @@ export async function runInterpreter( throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`); } catch (err) { - notify.error(err); + notifyService.getService().error(err); throw err; } } diff --git a/x-pack/legacy/plugins/canvas/public/services/index.ts b/x-pack/legacy/plugins/canvas/public/services/index.ts new file mode 100644 index 0000000000000..12c0a687bf308 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/services/index.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart } from '../../../../../../src/core/public'; +import { CanvasSetupDeps, CanvasStartDeps } from '../plugin'; +import { notifyServiceFactory } from './notify'; + +export type CanvasServiceFactory = ( + coreSetup: CoreSetup, + coreStart: CoreStart, + canvasSetupPlugins: CanvasSetupDeps, + canvasStartPlugins: CanvasStartDeps +) => Service; + +class CanvasServiceProvider { + private factory: CanvasServiceFactory; + private service: Service | undefined; + + constructor(factory: CanvasServiceFactory) { + this.factory = factory; + } + + start( + coreSetup: CoreSetup, + coreStart: CoreStart, + canvasSetupPlugins: CanvasSetupDeps, + canvasStartPlugins: CanvasStartDeps + ) { + this.service = this.factory(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins); + } + + getService(): Service { + if (!this.service) { + throw new Error('Service not ready'); + } + + return this.service; + } + + stop() { + this.service = undefined; + } +} + +export type ServiceFromProvider

= P extends CanvasServiceProvider ? T : never; + +export const services = { + notify: new CanvasServiceProvider(notifyServiceFactory), +}; + +export interface CanvasServices { + notify: ServiceFromProvider; +} + +export const startServices = ( + coreSetup: CoreSetup, + coreStart: CoreStart, + canvasSetupPlugins: CanvasSetupDeps, + canvasStartPlugins: CanvasStartDeps +) => { + Object.entries(services).forEach(([key, provider]) => + provider.start(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins) + ); +}; + +export const stopServices = () => { + Object.entries(services).forEach(([key, provider]) => provider.stop()); +}; + +export const { notify: notifyService } = services; diff --git a/x-pack/legacy/plugins/canvas/public/services/notify.ts b/x-pack/legacy/plugins/canvas/public/services/notify.ts new file mode 100644 index 0000000000000..3e18e2178a818 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/services/notify.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { CanvasServiceFactory } from '.'; +import { formatMsg } from '../../../../../../src/plugins/kibana_legacy/public'; +import { ToastInputFields } from '../../../../../../src/core/public'; + +const getToast = (err: Error | string, opts: ToastInputFields = {}) => { + const errData = (get(err, 'response') || err) as Error | string; + const errMsg = formatMsg(errData); + const { title, ...rest } = opts; + let text; + + if (title) { + text = errMsg; + } + + return { + ...rest, + title: title || errMsg, + text, + }; +}; + +interface NotifyService { + error: (err: string | Error, opts?: ToastInputFields) => void; + warning: (err: string | Error, opts?: ToastInputFields) => void; + info: (err: string | Error, opts?: ToastInputFields) => void; + success: (err: string | Error, opts?: ToastInputFields) => void; +} + +export const notifyServiceFactory: CanvasServiceFactory = (setup, start) => { + const toasts = start.notifications.toasts; + + return { + /* + * @param {(string | Object)} err: message or Error object + * @param {Object} opts: option to override toast title or icon, see https://github.com/elastic/kibana/blob/master/src/legacy/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md + */ + error(err, opts) { + toasts.addDanger(getToast(err, opts)); + }, + warning(err, opts) { + toasts.addWarning(getToast(err, opts)); + }, + info(err, opts) { + toasts.add(getToast(err, opts)); + }, + success(err, opts) { + toasts.addSuccess(getToast(err, opts)); + }, + }; +}; diff --git a/x-pack/legacy/plugins/canvas/public/state/actions/elements.js b/x-pack/legacy/plugins/canvas/public/state/actions/elements.js index 1798aaab22f06..f4a3393b8962d 100644 --- a/x-pack/legacy/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/legacy/plugins/canvas/public/state/actions/elements.js @@ -13,9 +13,9 @@ import { getPages, getNodeById, getNodes, getSelectedPageIndex } from '../select import { getValue as getResolvedArgsValue } from '../selectors/resolved_args'; import { getDefaultElement } from '../defaults'; import { ErrorStrings } from '../../../i18n'; -import { notify } from '../../lib/notify'; import { runInterpreter, interpretAst } from '../../lib/run_interpreter'; import { subMultitree } from '../../lib/aeroelastic/functional'; +import { services } from '../../services'; import { selectToplevelNodes } from './transient'; import * as args from './resolved_args'; @@ -134,7 +134,7 @@ const fetchRenderableWithContextFn = ({ dispatch }, element, ast, context) => { dispatch(getAction(renderable)); }) .catch(err => { - notify.error(err); + services.notify.getService().error(err); dispatch(getAction(err)); }); }; @@ -176,7 +176,7 @@ export const fetchAllRenderables = createThunk( return runInterpreter(ast, null, { castToRender: true }) .then(renderable => ({ path: argumentPath, value: renderable })) .catch(err => { - notify.error(err); + services.notify.getService().error(err); return { path: argumentPath, value: err }; }); }); @@ -293,7 +293,7 @@ const setAst = createThunk('setAst', ({ dispatch }, ast, element, pageId, doRend const expression = toExpression(ast); dispatch(setExpression(expression, element.id, pageId, doRender)); } catch (err) { - notify.error(err); + services.notify.getService().error(err); // TODO: remove this, may have been added just to cause a re-render, but why? dispatch(setExpression(element.expression, element.id, pageId, doRender)); diff --git a/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js b/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js index bcbfc3544981a..a197cdf893244 100644 --- a/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js +++ b/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js @@ -14,7 +14,7 @@ import { setAssets, resetAssets } from '../actions/assets'; import * as transientActions from '../actions/transient'; import * as resolvedArgsActions from '../actions/resolved_args'; import { update, updateAssets, updateWorkpad } from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; +import { services } from '../../services'; import { canUserWrite } from '../selectors/app'; const { esPersist: strings } = ErrorStrings; @@ -62,15 +62,15 @@ export const esPersistMiddleware = ({ getState }) => { const statusCode = err.response && err.response.status; switch (statusCode) { case 400: - return notify.error(err.response, { + return services.notify.getService().error(err.response, { title: strings.getSaveFailureTitle(), }); case 413: - return notify.error(strings.getTooLargeErrorMessage(), { + return services.notify.getService().error(strings.getTooLargeErrorMessage(), { title: strings.getSaveFailureTitle(), }); default: - return notify.error(err, { + return services.notify.getService().error(err, { title: strings.getUpdateFailureTitle(), }); } From a012ddf9df3035c499d4c1e2ca3f0e1ec87174a0 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Fri, 24 Apr 2020 17:04:36 +0100 Subject: [PATCH 11/13] [alerting] removes usage of any throughout Alerting Services code (#64161) This removes unneeded use of `any` throughout: 1. alerting 2. alerting_builtin 3. actions 4. task manager 5. event log It also adds a linting rule that will prevent us from adding more `any` in the future unless an explicit exemption is made. --- .eslintrc.js | 13 ++++ x-pack/legacy/plugins/actions/server/index.ts | 9 ++- .../legacy/plugins/alerting/server/index.ts | 9 ++- .../plugins/task_manager/server/index.ts | 17 ++++- .../plugins/task_manager/server/legacy.ts | 4 +- .../plugins/task_manager/server/migrations.ts | 4 +- x-pack/plugins/actions/common/types.ts | 2 + .../actions/server/action_type_registry.ts | 2 +- .../actions/server/actions_client.mock.ts | 7 +- .../plugins/actions/server/actions_client.ts | 4 +- .../server/builtin_action_types/email.test.ts | 12 ++-- .../server/builtin_action_types/email.ts | 8 +-- .../builtin_action_types/es_index.test.ts | 6 +- .../server/builtin_action_types/es_index.ts | 5 +- .../lib/post_pagerduty.ts | 2 +- .../lib/send_email.test.ts | 26 +++++--- .../builtin_action_types/lib/send_email.ts | 4 +- .../builtin_action_types/pagerduty.test.ts | 2 +- .../server/builtin_action_types/pagerduty.ts | 21 ++++-- .../builtin_action_types/server_log.test.ts | 2 +- .../servicenow/action_handlers.ts | 8 +-- .../servicenow/helpers.ts | 8 +-- .../servicenow/index.test.ts | 2 +- .../builtin_action_types/servicenow/index.ts | 2 +- .../servicenow/lib/index.ts | 4 +- .../builtin_action_types/servicenow/types.ts | 6 +- .../server/builtin_action_types/slack.test.ts | 27 +++++--- .../server/builtin_action_types/slack.ts | 2 +- .../builtin_action_types/webhook.test.ts | 30 +++++---- .../server/builtin_action_types/webhook.ts | 2 +- x-pack/plugins/actions/server/config.test.ts | 4 +- .../actions/server/constants/plugin.ts | 1 + .../actions/server/create_execute_function.ts | 10 +-- .../actions/server/lib/action_executor.ts | 12 ++-- .../actions/server/lib/executor_error.ts | 4 +- .../actions/server/lib/license_state.test.ts | 24 ++++--- .../actions/server/lib/task_runner_factory.ts | 8 +-- .../server/lib/validate_with_schema.test.ts | 8 +-- .../server/lib/validate_with_schema.ts | 65 ++++++++++--------- x-pack/plugins/actions/server/plugin.test.ts | 48 +++++++++----- x-pack/plugins/actions/server/plugin.ts | 9 ++- .../server/routes/_mock_handler_arguments.ts | 13 ++-- .../actions/server/routes/create.test.ts | 49 +++++++------- .../plugins/actions/server/routes/create.ts | 4 +- .../actions/server/routes/delete.test.ts | 34 +++++----- .../plugins/actions/server/routes/delete.ts | 4 +- .../plugins/actions/server/routes/execute.ts | 4 +- .../plugins/actions/server/routes/get.test.ts | 45 +++++++------ x-pack/plugins/actions/server/routes/get.ts | 4 +- .../actions/server/routes/get_all.test.ts | 16 ++--- .../plugins/actions/server/routes/get_all.ts | 4 +- .../server/routes/list_action_types.test.ts | 13 ++++ .../server/routes/list_action_types.ts | 4 +- .../actions/server/routes/update.test.ts | 24 +++---- .../plugins/actions/server/routes/update.ts | 4 +- x-pack/plugins/actions/server/types.ts | 29 +++++++-- .../actions/server/usage/actions_telemetry.ts | 2 + x-pack/plugins/alerting/common/alert.ts | 2 + .../server/alert_type_registry.test.ts | 2 +- .../alerting/server/alert_type_registry.ts | 2 +- .../alerting/server/alerts_client.mock.ts | 7 +- .../alerting/server/alerts_client.test.ts | 5 +- .../plugins/alerting/server/alerts_client.ts | 15 +++-- .../server/alerts_client_factory.test.ts | 26 ++++---- .../alerting/server/constants/plugin.ts | 2 + .../alerting/server/lib/license_state.test.ts | 4 +- .../server/lib/validate_alert_type_params.ts | 10 +-- x-pack/plugins/alerting/server/plugin.test.ts | 47 +++++++------- x-pack/plugins/alerting/server/plugin.ts | 9 ++- .../server/routes/_mock_handler_arguments.ts | 29 +++++++-- .../alerting/server/routes/create.test.ts | 4 +- .../plugins/alerting/server/routes/create.ts | 4 +- .../alerting/server/routes/delete.test.ts | 18 +++-- .../plugins/alerting/server/routes/delete.ts | 4 +- .../plugins/alerting/server/routes/disable.ts | 4 +- .../plugins/alerting/server/routes/enable.ts | 4 +- .../alerting/server/routes/find.test.ts | 17 +++-- x-pack/plugins/alerting/server/routes/find.ts | 4 +- .../alerting/server/routes/get.test.ts | 4 +- x-pack/plugins/alerting/server/routes/get.ts | 4 +- .../alerting/server/routes/get_alert_state.ts | 4 +- .../plugins/alerting/server/routes/health.ts | 4 +- .../server/routes/list_alert_types.test.ts | 11 ++++ .../server/routes/list_alert_types.ts | 4 +- .../alerting/server/routes/mute_all.ts | 4 +- .../alerting/server/routes/mute_instance.ts | 4 +- .../alerting/server/routes/unmute_all.ts | 4 +- .../alerting/server/routes/unmute_instance.ts | 4 +- .../plugins/alerting/server/routes/update.ts | 4 +- .../alerting/server/routes/update_api_key.ts | 4 +- .../server/task_runner/alert_task_instance.ts | 9 ++- .../task_runner/get_next_run_at.test.ts | 1 + .../server/task_runner/task_runner.ts | 6 +- .../task_runner/transform_action_params.ts | 2 +- .../alerting/server/test_utils/index.ts | 16 ++--- x-pack/plugins/alerting/server/types.ts | 17 ++++- .../alerting/server/usage/alerts_telemetry.ts | 7 +- .../index_threshold/alert_type_params.test.ts | 4 +- .../index_threshold/alert_type_params.ts | 4 +- .../lib/core_query_types.test.ts | 5 +- .../index_threshold/lib/core_query_types.ts | 12 +++- .../index_threshold/lib/time_series_query.ts | 7 +- .../lib/time_series_types.test.ts | 4 +- .../index_threshold/lib/time_series_types.ts | 6 +- .../index_threshold/routes/fields.ts | 2 +- .../index_threshold/routes/indices.ts | 17 +++-- .../routes/time_series_query.ts | 2 +- .../server/es/cluster_client_adapter.test.ts | 4 ++ .../server/es/cluster_client_adapter.ts | 48 +++++++++----- .../plugins/event_log/server/es/documents.ts | 2 +- .../server/event_log_start_service.test.ts | 4 +- .../plugins/event_log/server/event_logger.ts | 4 +- .../event_log/server/lib/ready_signal.test.ts | 6 +- x-pack/plugins/event_log/server/plugin.ts | 2 +- .../server/routes/_mock_handler_arguments.ts | 6 +- .../plugins/event_log/server/routes/find.ts | 4 +- .../task_manager/server/config.test.ts | 4 +- .../server/create_task_manager.ts | 5 +- .../server/lib/middleware.test.ts | 8 +-- .../lib/sanitize_task_definitions.test.ts | 6 +- x-pack/plugins/task_manager/server/plugin.ts | 2 +- .../mark_available_tasks_as_claimed.ts | 1 + x-pack/plugins/task_manager/server/task.ts | 27 +++++++- .../task_manager/server/task_events.ts | 10 +-- .../task_manager/server/task_manager.test.ts | 17 ++--- .../task_manager/server/task_manager.ts | 7 +- .../task_manager/server/task_runner.test.ts | 14 ++-- .../task_manager/server/task_runner.ts | 2 +- .../task_manager/server/task_store.test.ts | 48 ++++++++------ .../plugins/task_manager/server/task_store.ts | 48 +++++++------- .../task_manager/server/test_utils/index.ts | 10 +-- 131 files changed, 839 insertions(+), 541 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 81a883f3397e8..dfb4603ba95af 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -739,6 +739,19 @@ module.exports = { }, }, + /** + * Alerting Services overrides + */ + { + // typescript only for front and back end + files: [ + 'x-pack/{,legacy/}plugins/{alerting,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', + ], + rules: { + '@typescript-eslint/no-explicit-any': 'error', + }, + }, + /** * Lens overrides */ diff --git a/x-pack/legacy/plugins/actions/server/index.ts b/x-pack/legacy/plugins/actions/server/index.ts index 7235eda88149f..63dd6f99f9c24 100644 --- a/x-pack/legacy/plugins/actions/server/index.ts +++ b/x-pack/legacy/plugins/actions/server/index.ts @@ -6,8 +6,13 @@ import { Root } from 'joi'; import { Legacy } from 'kibana'; import mappings from './mappings.json'; +import { + LegacyPluginApi, + LegacyPluginSpec, + ArrayOrItem, +} from '../../../../../src/legacy/plugin_discovery/types'; -export function actions(kibana: any) { +export function actions(kibana: LegacyPluginApi): ArrayOrItem { return new kibana.Plugin({ id: 'actions', configPrefix: 'xpack.actions', @@ -29,5 +34,5 @@ export function actions(kibana: any) { uiExports: { mappings, }, - }); + } as Legacy.PluginSpecOptions); } diff --git a/x-pack/legacy/plugins/alerting/server/index.ts b/x-pack/legacy/plugins/alerting/server/index.ts index 5bf7cda51bda6..065af7dedebd9 100644 --- a/x-pack/legacy/plugins/alerting/server/index.ts +++ b/x-pack/legacy/plugins/alerting/server/index.ts @@ -7,8 +7,13 @@ import { Legacy } from 'kibana'; import { Root } from 'joi'; import mappings from './mappings.json'; +import { + LegacyPluginApi, + LegacyPluginSpec, + ArrayOrItem, +} from '../../../../../src/legacy/plugin_discovery/types'; -export function alerting(kibana: any) { +export function alerting(kibana: LegacyPluginApi): ArrayOrItem { return new kibana.Plugin({ id: 'alerting', configPrefix: 'xpack.alerting', @@ -31,5 +36,5 @@ export function alerting(kibana: any) { uiExports: { mappings, }, - }); + } as Legacy.PluginSpecOptions); } diff --git a/x-pack/legacy/plugins/task_manager/server/index.ts b/x-pack/legacy/plugins/task_manager/server/index.ts index ff25d8a1e0e5d..3ea687f7003f4 100644 --- a/x-pack/legacy/plugins/task_manager/server/index.ts +++ b/x-pack/legacy/plugins/task_manager/server/index.ts @@ -15,19 +15,26 @@ export { LegacyTaskManagerApi, getTaskManagerSetup, getTaskManagerStart } from ' // Once all plugins are migrated to NP, this can be removed // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TaskManager } from '../../../../plugins/task_manager/server/task_manager'; +import { + LegacyPluginApi, + LegacyPluginSpec, + ArrayOrItem, +} from '../../../../../src/legacy/plugin_discovery/types'; const savedObjectSchemas = { task: { hidden: true, isNamespaceAgnostic: true, convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`, + // legacy config is marked as any in core, no choice here + // eslint-disable-next-line @typescript-eslint/no-explicit-any indexPattern(config: any) { return config.get('xpack.task_manager.index'); }, }, }; -export function taskManager(kibana: any) { +export function taskManager(kibana: LegacyPluginApi): ArrayOrItem { return new kibana.Plugin({ id: 'task_manager', require: ['kibana', 'elasticsearch', 'xpack_main'], @@ -58,7 +65,11 @@ export function taskManager(kibana: any) { // instead we will start the internal Task Manager plugin when // all legacy plugins have finished initializing // Once all plugins are migrated to NP, this can be removed - this.kbnServer.afterPluginsInit(() => { + + // the typing for the lagcy server isn't quite correct, so + // we'll bypase it for now + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this as any).kbnServer.afterPluginsInit(() => { taskManagerPlugin.start(); }); return taskManagerPlugin; @@ -71,5 +82,5 @@ export function taskManager(kibana: any) { migrations, savedObjectSchemas, }, - }); + } as Legacy.PluginSpecOptions); } diff --git a/x-pack/legacy/plugins/task_manager/server/legacy.ts b/x-pack/legacy/plugins/task_manager/server/legacy.ts index cd2047b757e61..0d50828004a94 100644 --- a/x-pack/legacy/plugins/task_manager/server/legacy.ts +++ b/x-pack/legacy/plugins/task_manager/server/legacy.ts @@ -49,10 +49,10 @@ export function createLegacyApi(legacyTaskManager: Promise): Legacy fetch: (opts: SearchOpts) => legacyTaskManager.then((tm: TaskManager) => tm.fetch(opts)), get: (id: string) => legacyTaskManager.then((tm: TaskManager) => tm.get(id)), remove: (id: string) => legacyTaskManager.then((tm: TaskManager) => tm.remove(id)), - schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: any) => + schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: object) => legacyTaskManager.then((tm: TaskManager) => tm.schedule(taskInstance, options)), runNow: (taskId: string) => legacyTaskManager.then((tm: TaskManager) => tm.runNow(taskId)), - ensureScheduled: (taskInstance: TaskInstanceWithId, options?: any) => + ensureScheduled: (taskInstance: TaskInstanceWithId, options?: object) => legacyTaskManager.then((tm: TaskManager) => tm.ensureScheduled(taskInstance, options)), }; } diff --git a/x-pack/legacy/plugins/task_manager/server/migrations.ts b/x-pack/legacy/plugins/task_manager/server/migrations.ts index 97c4f97f59c58..1c2cf73d0fe13 100644 --- a/x-pack/legacy/plugins/task_manager/server/migrations.ts +++ b/x-pack/legacy/plugins/task_manager/server/migrations.ts @@ -7,7 +7,7 @@ import { SavedObject } from '../../../../../src/core/server'; export const migrations = { task: { - '7.4.0': (doc: SavedObject>) => ({ + '7.4.0': (doc: SavedObject>) => ({ ...doc, updated_at: new Date().toISOString(), }), @@ -18,7 +18,7 @@ export const migrations = { function moveIntervalIntoSchedule({ attributes: { interval, ...attributes }, ...doc -}: SavedObject>) { +}: SavedObject>) { return { ...doc, attributes: { diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 61b338d47b9f5..49e8f3e80b14a 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -19,6 +19,8 @@ export interface ActionResult { id: string; actionTypeId: string; name: string; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any config: Record; isPreconfigured: boolean; } diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index c1d979feacc1d..735ec349837a9 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -81,7 +81,7 @@ export class ActionTypeRegistry { title: actionType.name, type: `actions:${actionType.id}`, maxAttempts: actionType.maxAttempts || 1, - getRetry(attempts: number, error: any) { + getRetry(attempts: number, error: unknown) { if (error instanceof ExecutorError) { return error.retry == null ? false : error.retry; } diff --git a/x-pack/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts index 431bfb1e99c3b..64b43e1ab6bbc 100644 --- a/x-pack/plugins/actions/server/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client.mock.ts @@ -7,9 +7,10 @@ import { ActionsClient } from './actions_client'; type ActionsClientContract = PublicMethodsOf; +export type ActionsClientMock = jest.Mocked; const createActionsClientMock = () => { - const mocked: jest.Mocked = { + const mocked: ActionsClientMock = { create: jest.fn(), get: jest.fn(), delete: jest.fn(), @@ -19,6 +20,8 @@ const createActionsClientMock = () => { return mocked; }; -export const actionsClientMock = { +export const actionsClientMock: { + create: () => ActionsClientMock; +} = { create: createActionsClientMock, }; diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index f52e293296955..618bc8a85e856 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -135,7 +135,7 @@ export class ActionsClient { id, actionTypeId: result.attributes.actionTypeId as string, name: result.attributes.name as string, - config: result.attributes.config as Record, + config: result.attributes.config as Record, isPreconfigured: false, }; } @@ -228,7 +228,7 @@ async function injectExtraFindData( scopedClusterClient: IScopedClusterClient, actionResults: ActionResult[] ): Promise { - const aggs: Record = {}; + const aggs: Record = {}; for (const actionResult of actionResults) { aggs[actionResult.id] = { filter: { diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 658f8f3fd8cf9..265ff267f222e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -30,7 +30,7 @@ const NO_OP_FN = () => {}; const services = { log: NO_OP_FN, - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; @@ -52,7 +52,7 @@ describe('actionTypeRegistry.get() works', () => { describe('config validation', () => { test('config validation succeeds when config is valid', () => { - const config: Record = { + const config: Record = { service: 'gmail', from: 'bob@example.com', }; @@ -74,7 +74,7 @@ describe('config validation', () => { }); test('config validation fails when config is not valid', () => { - const baseConfig: Record = { + const baseConfig: Record = { from: 'bob@example.com', }; @@ -177,7 +177,7 @@ describe('config validation', () => { describe('secrets validation', () => { test('secrets validation succeeds when secrets is valid', () => { - const secrets: Record = { + const secrets: Record = { user: 'bob', password: 'supersecret', }; @@ -185,7 +185,7 @@ describe('secrets validation', () => { }); test('secrets validation succeeds when secrets props are null/undefined', () => { - const secrets: Record = { + const secrets: Record = { user: null, password: null, }; @@ -197,7 +197,7 @@ describe('secrets validation', () => { describe('params validation', () => { test('params validation succeeds when params is valid', () => { - const params: Record = { + const params: Record = { to: ['bob@example.com'], subject: 'this is a test', message: 'this is the message', diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts index ca8d089ad2946..7ddb123a4d780 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -30,10 +30,10 @@ const ConfigSchema = schema.object(ConfigSchemaProps); function validateConfig( configurationUtilities: ActionsConfigurationUtilities, - configObject: any + configObject: unknown ): string | void { // avoids circular reference ... - const config: ActionTypeConfigType = configObject; + const config = configObject as ActionTypeConfigType; // Make sure service is set, or if not, both host/port must be set. // If service is set, host/port are ignored, when the email is sent. @@ -95,9 +95,9 @@ const ParamsSchema = schema.object( } ); -function validateParams(paramsObject: any): string | void { +function validateParams(paramsObject: unknown): string | void { // avoids circular reference ... - const params: ActionParamsType = paramsObject; + const params = paramsObject as ActionParamsType; const { to, cc, bcc } = params; const addrs = to.length + cc.length + bcc.length; diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts index ec495aed7675a..ed57e44c3f0b3 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -43,7 +43,7 @@ describe('actionTypeRegistry.get() works', () => { describe('config validation', () => { test('config validation succeeds when config is valid', () => { - const config: Record = { + const config: Record = { index: 'testing-123', refresh: false, }; @@ -97,7 +97,7 @@ describe('config validation', () => { }); test('config validation fails when config is not valid', () => { - const baseConfig: Record = { + const baseConfig: Record = { indeX: 'bob', }; @@ -111,7 +111,7 @@ describe('config validation', () => { describe('params validation', () => { test('params validation succeeds when params is valid', () => { - const params: Record = { + const params: Record = { documents: [{ rando: 'thing' }], }; expect(validateParams(actionType, params)).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index ff7b27b3f51fc..32f5e23015700 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -72,13 +72,12 @@ async function executor( bulkBody.push(document); } - const bulkParams: any = { + const bulkParams: unknown = { index, body: bulkBody, + refresh: config.refresh, }; - bulkParams.refresh = config.refresh; - let result; try { result = await services.callCluster('bulk', bulkParams); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts index cc9d36ff86342..92f88ebe0be22 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts @@ -9,7 +9,7 @@ import { Services } from '../../types'; interface PostPagerdutyOptions { apiUrl: string; - data: any; + data: unknown; headers: Record; services: Services; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index 42160dc2fc22b..a02f79a49e8e2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -63,12 +63,15 @@ describe('send_email module', () => { }); test('handles unauthenticated email using not secure host/port', async () => { - const sendEmailOptions = getSendEmailOptions(); + const sendEmailOptions = getSendEmailOptions({ + transport: { + host: 'example.com', + port: 1025, + }, + }); delete sendEmailOptions.transport.service; delete sendEmailOptions.transport.user; delete sendEmailOptions.transport.password; - sendEmailOptions.transport.host = 'example.com'; - sendEmailOptions.transport.port = 1025; const result = await sendEmail(mockLogger, sendEmailOptions); expect(result).toBe(sendMailMockResult); expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` @@ -105,13 +108,17 @@ describe('send_email module', () => { }); test('handles unauthenticated email using secure host/port', async () => { - const sendEmailOptions = getSendEmailOptions(); + const sendEmailOptions = getSendEmailOptions({ + transport: { + host: 'example.com', + port: 1025, + secure: true, + }, + }); delete sendEmailOptions.transport.service; delete sendEmailOptions.transport.user; delete sendEmailOptions.transport.password; - sendEmailOptions.transport.host = 'example.com'; - sendEmailOptions.transport.port = 1025; - sendEmailOptions.transport.secure = true; + const result = await sendEmail(mockLogger, sendEmailOptions); expect(result).toBe(sendMailMockResult); expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` @@ -154,19 +161,22 @@ describe('send_email module', () => { }); }); -function getSendEmailOptions(): any { +function getSendEmailOptions({ content = {}, routing = {}, transport = {} } = {}) { return { content: { + ...content, message: 'a message', subject: 'a subject', }, routing: { + ...routing, from: 'fred@example.com', to: ['jim@example.com'], cc: ['bob@example.com', 'robert@example.com'], bcc: [], }, transport: { + ...transport, service: 'whatever', user: 'elastic', password: 'changeme', diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index ffbf7485a8b0b..869db34f034ae 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -43,13 +43,13 @@ export interface Content { } // send an email -export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { +export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { const { transport, routing, content } = options; const { service, host, port, secure, user, password } = transport; const { from, to, cc, bcc } = routing; const { subject, message } = content; - const transportConfig: Record = {}; + const transportConfig: Record = {}; if (user != null && password != null) { transportConfig.auth = { diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts index e5521558bc2da..e54c8179ae7b4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -22,7 +22,7 @@ const postPagerdutyMock = postPagerduty as jest.Mock; const ACTION_TYPE_ID = '.pagerduty'; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index f4d69a4a39e40..0c8802060164d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -68,9 +68,8 @@ const ParamsSchema = schema.object( { validate: validateParams } ); -function validateParams(paramsObject: any): string | void { - const params: ActionParamsType = paramsObject; - const { timestamp } = params; +function validateParams(paramsObject: unknown): string | void { + const { timestamp } = paramsObject as ActionParamsType; if (timestamp != null) { try { const date = Date.parse(timestamp); @@ -218,11 +217,23 @@ async function executor( const AcknowledgeOrResolve = new Set([EVENT_ACTION_ACKNOWLEDGE, EVENT_ACTION_RESOLVE]); -function getBodyForEventAction(actionId: string, params: ActionParamsType): any { +function getBodyForEventAction(actionId: string, params: ActionParamsType): unknown { const eventAction = params.eventAction || EVENT_ACTION_TRIGGER; const dedupKey = params.dedupKey || `action:${actionId}`; - const data: any = { + const data: { + event_action: ActionParamsType['eventAction']; + dedup_key: string; + payload?: { + summary: string; + source: string; + severity: string; + timestamp?: string; + component?: string; + group?: string; + class?: string; + }; + } = { event_action: eventAction, dedup_key: dedupKey, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts index bb806f8ae36fc..3ce01c59596f6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -91,7 +91,7 @@ describe('execute()', () => { await actionType.executor({ actionId, services: { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }, params: { message: 'message text here', level: 'info' }, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts index fb296089e9ec5..9166f53cf757e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts @@ -57,14 +57,14 @@ export const handleCreateIncident = async ({ comments && Array.isArray(comments) && comments.length > 0 && - mapping.get('comments').actionType !== 'nothing' + mapping.get('comments')?.actionType !== 'nothing' ) { comments = transformComments(comments, params, ['informationAdded']); res.comments = [ ...(await createComments( serviceNow, res.incidentId, - mapping.get('comments').target, + mapping.get('comments')!.target, comments )), ]; @@ -103,11 +103,11 @@ export const handleUpdateIncident = async ({ comments && Array.isArray(comments) && comments.length > 0 && - mapping.get('comments').actionType !== 'nothing' + mapping.get('comments')?.actionType !== 'nothing' ) { comments = transformComments(comments, params, ['informationAdded']); res.comments = [ - ...(await createComments(serviceNow, incidentId, mapping.get('comments').target, comments)), + ...(await createComments(serviceNow, incidentId, mapping.get('comments')!.target, comments)), ]; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts index 750fda93b60d6..0a26996ea8d69 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts @@ -39,7 +39,7 @@ export const buildMap = (mapping: MapEntry[]): Mapping => { }, new Map()); }; -export const mapParams = (params: any, mapping: Mapping) => { +export const mapParams = (params: Record, mapping: Mapping) => { return Object.keys(params).reduce((prev: KeyAny, curr: string): KeyAny => { const field = mapping.get(curr); if (field) { @@ -61,11 +61,11 @@ export const prepareFieldsForTransformation = ({ defaultPipes = ['informationCreated'], }: PrepareFieldsForTransformArgs): PipedField[] => { return Object.keys(params.incident) - .filter(p => mapping.get(p).actionType !== 'nothing') + .filter(p => mapping.get(p)!.actionType !== 'nothing') .map(p => ({ key: p, - value: params.incident[p], - actionType: mapping.get(p).actionType, + value: params.incident[p] as string, + actionType: mapping.get(p)!.actionType, pipes: [...defaultPipes], })) .map(p => ({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts index 1a23354e6490d..08b837cf8d0a5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts @@ -22,7 +22,7 @@ jest.mock('./action_handlers'); const handleIncidentMock = handleIncident as jest.Mock; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index a63c2fd3a6ceb..5066190d4fe56 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -85,7 +85,7 @@ async function serviceNowExecutor( const { comments, incidentId, ...restParams } = params; const mapping = buildMap(configurationMapping); - const incident = mapParams(restParams, mapping); + const incident = mapParams((restParams as unknown) as Record, mapping); const serviceNow = new ServiceNow({ url: apiUrl, username, password }); const handlerInput = { diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts index cc07a0b90330d..ed9cfe67a19a1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts @@ -49,14 +49,14 @@ class ServiceNow { }: { url: string; method?: Method; - data?: any; + data?: unknown; }): Promise { const res = await this.axios(url, { method, data }); this._throwIfNotAlive(res.status, res.headers['content-type']); return res; } - private _patch({ url, data }: { url: string; data: any }): Promise { + private _patch({ url, data }: { url: string; data: unknown }): Promise { return this._request({ url, method: 'patch', diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index 71b05be8f3e4d..c5ef282aeffa7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -30,10 +30,10 @@ export type CasesConfigurationType = TypeOf; export type MapEntry = TypeOf; export type Comment = TypeOf; -export type Mapping = Map; +export type Mapping = Map>; export interface Params extends ExecutorParams { - incident: Record; + incident: Record; } export interface CreateHandlerArguments { serviceNow: ServiceNow; @@ -66,7 +66,7 @@ export interface AppendFieldArgs { } export interface KeyAny { - [index: string]: string; + [index: string]: unknown; } export interface AppendInformationFieldArgs { diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index 49b0b84e9dbb5..22c4b63474fdc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; +import { + ActionType, + Services, + ActionTypeExecutorOptions, + ActionTypeExecutorResult, +} from '../types'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { validateParams, validateSecrets } from '../lib'; import { getActionType } from './slack'; @@ -13,7 +18,7 @@ import { actionsConfigMock } from '../actions_config.mock'; const ACTION_TYPE_ID = '.slack'; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; @@ -21,7 +26,7 @@ let actionType: ActionType; beforeAll(() => { actionType = getActionType({ - async executor(options: ActionTypeExecutorOptions): Promise {}, + async executor() {}, configurationUtilities: actionsConfigMock.create(), }); }); @@ -117,7 +122,7 @@ describe('validateActionTypeSecrets()', () => { describe('execute()', () => { beforeAll(() => { - async function mockSlackExecutor(options: ActionTypeExecutorOptions): Promise { + async function mockSlackExecutor(options: ActionTypeExecutorOptions) { const { params } = options; const { message } = params; if (message == null) throw new Error('message property required in parameter'); @@ -130,7 +135,9 @@ describe('execute()', () => { return { text: `slack mockExecutor success: ${message}`, - }; + actionId: '', + status: 'ok', + } as ActionTypeExecutorResult; } actionType = getActionType({ @@ -148,10 +155,12 @@ describe('execute()', () => { params: { message: 'this invocation should succeed' }, }); expect(response).toMatchInlineSnapshot(` -Object { - "text": "slack mockExecutor success: this invocation should succeed", -} -`); + Object { + "actionId": "", + "status": "ok", + "text": "slack mockExecutor success: this invocation should succeed", + } + `); }); test('calls the mock executor with failure', async () => { diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index e51ef3f67bd65..edf3e485f3c57 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -156,7 +156,7 @@ async function slackExecutor( return successResult(actionId, result); } -function successResult(actionId: string, data: any): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { return { status: 'ok', data, actionId }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 03658b3b1dd85..1beaf92f3f48b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -22,7 +22,7 @@ const axiosRequestMock = axios.request as jest.Mock; const ACTION_TYPE_ID = '.webhook'; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; @@ -44,7 +44,7 @@ describe('actionType', () => { describe('secrets validation', () => { test('succeeds when secrets is valid', () => { - const secrets: Record = { + const secrets: Record = { user: 'bob', password: 'supersecret', }; @@ -60,20 +60,18 @@ describe('secrets validation', () => { }); test('succeeds when basic authentication credentials are omitted', () => { - expect(() => { - validateSecrets(actionType, {}).toEqual({}); - }); + expect(validateSecrets(actionType, {})).toEqual({ password: null, user: null }); }); }); describe('config validation', () => { - const defaultValues: Record = { + const defaultValues: Record = { headers: null, method: 'post', }; test('config validation passes when only required fields are provided', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', }; expect(validateConfig(actionType, config)).toEqual({ @@ -84,7 +82,7 @@ describe('config validation', () => { test('config validation passes when valid methods are provided', () => { ['post', 'put'].forEach(method => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', method, }; @@ -96,7 +94,7 @@ describe('config validation', () => { }); test('should validate and throw error when method on config is invalid', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', method: 'https', }; @@ -110,7 +108,7 @@ describe('config validation', () => { }); test('config validation passes when a url is specified', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', }; expect(validateConfig(actionType, config)).toEqual({ @@ -120,6 +118,8 @@ describe('config validation', () => { }); test('config validation passes when valid headers are provided', () => { + // any for testing + // eslint-disable-next-line @typescript-eslint/no-explicit-any const config: Record = { url: 'http://mylisteningserver:9200/endpoint', headers: { @@ -133,7 +133,7 @@ describe('config validation', () => { }); test('should validate and throw error when headers on config is invalid', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', headers: 'application/json', }; @@ -147,6 +147,8 @@ describe('config validation', () => { }); test('config validation passes when kibana config whitelists the url', () => { + // any for testing + // eslint-disable-next-line @typescript-eslint/no-explicit-any const config: Record = { url: 'http://mylisteningserver.com:9200/endpoint', headers: { @@ -171,6 +173,8 @@ describe('config validation', () => { }, }); + // any for testing + // eslint-disable-next-line @typescript-eslint/no-explicit-any const config: Record = { url: 'http://mylisteningserver.com:9200/endpoint', headers: { @@ -188,12 +192,12 @@ describe('config validation', () => { describe('params validation', () => { test('param validation passes when no fields are provided as none are required', () => { - const params: Record = {}; + const params: Record = {}; expect(validateParams(actionType, params)).toEqual({}); }); test('params validation passes when a valid body is provided', () => { - const params: Record = { + const params: Record = { body: 'count: {{ctx.payload.hits.total}}', }; expect(validateParams(actionType, params)).toEqual({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index 6173edc2df15a..0191ff1d9dbc1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -160,7 +160,7 @@ export async function executor( } // Action Executor Result w/ internationalisation -function successResult(actionId: string, data: any): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { return { status: 'ok', data, actionId }; } diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 51e87dbd75b48..161a6c31d4e59 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -7,7 +7,7 @@ import { configSchema } from './config'; describe('config validation', () => { test('action defaults', () => { - const config: Record = {}; + const config: Record = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { "enabled": true, @@ -23,7 +23,7 @@ describe('config validation', () => { }); test('action with preconfigured actions', () => { - const config: Record = { + const config: Record = { preconfigured: [ { id: 'my-slack1', diff --git a/x-pack/plugins/actions/server/constants/plugin.ts b/x-pack/plugins/actions/server/constants/plugin.ts index 68082ccaa1399..7d20eb6990247 100644 --- a/x-pack/plugins/actions/server/constants/plugin.ts +++ b/x-pack/plugins/actions/server/constants/plugin.ts @@ -9,6 +9,7 @@ import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/const export const PLUGIN = { ID: 'actions', MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => i18n.translate('xpack.actions.appName', { defaultMessage: 'Actions', diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 4a9ddf412b7cc..ac324587c5f4a 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from '../../../../src/core/server'; +import { SavedObjectsClientContract, KibanaRequest } from '../../../../src/core/server'; import { TaskManagerStartContract } from '../../task_manager/server'; import { GetBasePathFunction, @@ -15,7 +15,7 @@ import { interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; - getScopedSavedObjectsClient: (request: any) => SavedObjectsClientContract; + getScopedSavedObjectsClient: (request: KibanaRequest) => SavedObjectsClientContract; getBasePath: GetBasePathFunction; isESOUsingEphemeralEncryptionKey: boolean; actionTypeRegistry: ActionTypeRegistryContract; @@ -24,7 +24,7 @@ interface CreateExecuteFunctionOptions { export interface ExecuteOptions { id: string; - params: Record; + params: Record; spaceId: string; apiKey: string | null; } @@ -52,7 +52,7 @@ export function createExecuteFunction({ // Since we're using API keys and accessing elasticsearch can only be done // via a request, we're faking one with the proper authorization headers. - const fakeRequest: any = { + const fakeRequest: unknown = { headers: requestHeaders, getBasePath: () => getBasePath(spaceId), path: '/', @@ -67,7 +67,7 @@ export function createExecuteFunction({ }, }; - const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest); + const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest as KibanaRequest); const actionTypeId = await getActionTypeId(id); actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index a33fb8830a930..ac574decdba71 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -32,7 +32,7 @@ export interface ActionExecutorContext { export interface ExecuteOptions { actionId: string; request: KibanaRequest; - params: Record; + params: Record; } export type ActionExecutorContract = PublicMethodsOf; @@ -93,9 +93,9 @@ export class ActionExecutor { actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); const actionType = actionTypeRegistry.get(actionTypeId); - let validatedParams: Record; - let validatedConfig: Record; - let validatedSecrets: Record; + let validatedParams: Record; + let validatedConfig: Record; + let validatedSecrets: Record; try { validatedParams = validateParams(actionType, params); @@ -174,8 +174,8 @@ function actionErrorToMessage(result: ActionTypeExecutorResult): string { interface ActionInfo { actionTypeId: string; name: string; - config: any; - secrets: any; + config: unknown; + secrets: unknown; } async function getActionInfo( diff --git a/x-pack/plugins/actions/server/lib/executor_error.ts b/x-pack/plugins/actions/server/lib/executor_error.ts index 56ccbf14e6a45..c25033b2a27a2 100644 --- a/x-pack/plugins/actions/server/lib/executor_error.ts +++ b/x-pack/plugins/actions/server/lib/executor_error.ts @@ -5,9 +5,9 @@ */ export class ExecutorError extends Error { - readonly data?: any; + readonly data?: unknown; readonly retry: boolean | Date; - constructor(message?: string, data?: any, retry: boolean | Date = false) { + constructor(message?: string, data?: unknown, retry: boolean | Date = false) { super(message); this.data = data; this.retry = retry; diff --git a/x-pack/plugins/actions/server/lib/license_state.test.ts b/x-pack/plugins/actions/server/lib/license_state.test.ts index eb10e69a444e8..0a474ec3ae3ea 100644 --- a/x-pack/plugins/actions/server/lib/license_state.test.ts +++ b/x-pack/plugins/actions/server/lib/license_state.test.ts @@ -5,16 +5,16 @@ */ import { ActionType } from '../types'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; import { LicenseState, ILicenseState } from './license_state'; import { licensingMock } from '../../../licensing/server/mocks'; import { ILicense } from '../../../licensing/server'; describe('checkLicense()', () => { - let getRawLicense: any; + const getRawLicense = jest.fn(); beforeEach(() => { - getRawLicense = jest.fn(); + jest.resetAllMocks(); }); describe('status is LICENSE_STATUS_INVALID', () => { @@ -53,7 +53,7 @@ describe('checkLicense()', () => { }); describe('isLicenseValidForActionType', () => { - let license: BehaviorSubject; + let license: Subject; let licenseState: ILicenseState; const fooActionType: ActionType = { id: 'foo', @@ -63,7 +63,7 @@ describe('isLicenseValidForActionType', () => { }; beforeEach(() => { - license = new BehaviorSubject(null as any); + license = new Subject(); licenseState = new LicenseState(license); }); @@ -75,7 +75,7 @@ describe('isLicenseValidForActionType', () => { }); test('should return false when license not available', () => { - license.next({ isAvailable: false } as any); + license.next(createUnavailableLicense()); expect(licenseState.isLicenseValidForActionType(fooActionType)).toEqual({ isValid: false, reason: 'unavailable', @@ -114,7 +114,7 @@ describe('isLicenseValidForActionType', () => { }); describe('ensureLicenseForActionType()', () => { - let license: BehaviorSubject; + let license: Subject; let licenseState: ILicenseState; const fooActionType: ActionType = { id: 'foo', @@ -124,7 +124,7 @@ describe('ensureLicenseForActionType()', () => { }; beforeEach(() => { - license = new BehaviorSubject(null as any); + license = new Subject(); licenseState = new LicenseState(license); }); @@ -137,7 +137,7 @@ describe('ensureLicenseForActionType()', () => { }); test('should throw when license not available', () => { - license.next({ isAvailable: false } as any); + license.next(createUnavailableLicense()); expect(() => licenseState.ensureLicenseForActionType(fooActionType) ).toThrowErrorMatchingInlineSnapshot( @@ -175,3 +175,9 @@ describe('ensureLicenseForActionType()', () => { licenseState.ensureLicenseForActionType(fooActionType); }); }); + +function createUnavailableLicense() { + const unavailableLicense = licensingMock.createLicenseMock(); + unavailableLicense.isAvailable = false; + return unavailableLicense; +} diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index e2a6128aea203..08c5b90edbcb7 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -6,7 +6,7 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; -import { Logger, CoreStart } from '../../../../../src/core/server'; +import { Logger, CoreStart, KibanaRequest } from '../../../../../src/core/server'; import { RunContext } from '../../../task_manager/server'; import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; import { ActionTypeDisabledError } from './errors'; @@ -60,7 +60,7 @@ export class TaskRunnerFactory { return { async run() { - const { spaceId, actionTaskParamsId } = taskInstance.params; + const { spaceId, actionTaskParamsId } = taskInstance.params as Record; const namespace = spaceIdToNamespace(spaceId); const { @@ -78,7 +78,7 @@ export class TaskRunnerFactory { // Since we're using API keys and accessing elasticsearch can only be done // via a request, we're faking one with the proper authorization headers. - const fakeRequest: any = { + const fakeRequest = ({ headers: requestHeaders, getBasePath: () => getBasePath(spaceId), path: '/', @@ -91,7 +91,7 @@ export class TaskRunnerFactory { url: '/', }, }, - }; + } as unknown) as KibanaRequest; let executorResult: ActionTypeExecutorResult; try { diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts index b7d408985ed9f..1ccd25664374d 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts @@ -49,7 +49,7 @@ test('should validate when there are no individual validators', () => { }); test('should validate when validators return incoming value', () => { - const selfValidator = { validate: (value: any) => value }; + const selfValidator = { validate: (value: Record) => value }; const actionType: ActionType = { id: 'foo', name: 'bar', @@ -76,8 +76,8 @@ test('should validate when validators return incoming value', () => { }); test('should validate when validators return different values', () => { - const returnedValue: any = { something: { shaped: 'differently' } }; - const selfValidator = { validate: (value: any) => returnedValue }; + const returnedValue = { something: { shaped: 'differently' } }; + const selfValidator = { validate: () => returnedValue }; const actionType: ActionType = { id: 'foo', name: 'bar', @@ -105,7 +105,7 @@ test('should validate when validators return different values', () => { test('should throw with expected error when validators fail', () => { const erroringValidator = { - validate: (value: any) => { + validate: () => { throw new Error('test error'); }, }; diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.ts index 2306a726229e3..021c460f4c815 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.ts @@ -7,15 +7,15 @@ import Boom from 'boom'; import { ActionType } from '../types'; -export function validateParams(actionType: ActionType, value: any) { +export function validateParams(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'params', value); } -export function validateConfig(actionType: ActionType, value: any) { +export function validateConfig(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'config', value); } -export function validateSecrets(actionType: ActionType, value: any) { +export function validateSecrets(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'secrets', value); } @@ -24,33 +24,40 @@ type ValidKeys = 'params' | 'config' | 'secrets'; function validateWithSchema( actionType: ActionType, key: ValidKeys, - value: any -): Record { - if (actionType.validate == null) return value; - - let name; - try { - switch (key) { - case 'params': - name = 'action params'; - if (actionType.validate.params == null) return value; - return actionType.validate.params.validate(value); - - case 'config': - name = 'action type config'; - if (actionType.validate.config == null) return value; - return actionType.validate.config.validate(value); - - case 'secrets': - name = 'action type secrets'; - if (actionType.validate.secrets == null) return value; - return actionType.validate.secrets.validate(value); + value: unknown +): Record { + if (actionType.validate) { + let name; + try { + switch (key) { + case 'params': + name = 'action params'; + if (actionType.validate.params) { + return actionType.validate.params.validate(value); + } + break; + case 'config': + name = 'action type config'; + if (actionType.validate.config) { + return actionType.validate.config.validate(value); + } + + break; + case 'secrets': + name = 'action type secrets'; + if (actionType.validate.secrets) { + return actionType.validate.secrets.validate(value); + } + break; + default: + // should never happen, but left here for future-proofing + throw new Error(`invalid actionType validate key: ${key}`); + } + } catch (err) { + // we can't really i18n this yet, since the err.message isn't i18n'd itself + throw Boom.badRequest(`error validating ${name}: ${err.message}`); } - } catch (err) { - // we can't really i18n this yet, since the err.message isn't i18n'd itself - throw Boom.badRequest(`error validating ${name}: ${err.message}`); } - // should never happen, but left here for future-proofing - throw new Error(`invalid actionType validate key: ${key}`); + return value as Record; } diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index fa5b2f9399a4d..2b334953063d1 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginInitializerContext, RequestHandlerContext } from '../../../../src/core/server'; import { coreMock, httpServerMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; @@ -72,7 +72,9 @@ describe('Actions Plugin', () => { }); it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => { - await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, pluginsSetup); expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true); expect(context.logger.get().warn).toHaveBeenCalledWith( 'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.' @@ -81,7 +83,9 @@ describe('Actions Plugin', () => { describe('routeHandlerContext.getActionsClient()', () => { it('should not throw error when ESO plugin not using a generated key', async () => { - await plugin.setup(coreSetup, { + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, { ...pluginsSetup, encryptedSavedObjects: { ...pluginsSetup.encryptedSavedObjects, @@ -93,8 +97,8 @@ describe('Actions Plugin', () => { const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0]; expect(handler[0]).toEqual('actions'); - const actionsContextHandler = (await handler[1]( - { + const actionsContextHandler = ((await handler[1]( + ({ core: { savedObjects: { client: {}, @@ -103,32 +107,34 @@ describe('Actions Plugin', () => { adminClient: jest.fn(), }, }, - } as any, + } as unknown) as RequestHandlerContext, httpServerMock.createKibanaRequest(), httpServerMock.createResponseFactory() - )) as any; - actionsContextHandler.getActionsClient(); + )) as unknown) as RequestHandlerContext['actions']; + actionsContextHandler!.getActionsClient(); }); it('should throw error when ESO plugin using a generated key', async () => { - await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, pluginsSetup); expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1); const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0]; expect(handler[0]).toEqual('actions'); - const actionsContextHandler = (await handler[1]( - { + const actionsContextHandler = ((await handler[1]( + ({ core: { savedObjects: { client: {}, }, }, - } as any, + } as unknown) as RequestHandlerContext, httpServerMock.createKibanaRequest(), httpServerMock.createResponseFactory() - )) as any; - expect(() => actionsContextHandler.getActionsClient()).toThrowErrorMatchingInlineSnapshot( + )) as unknown) as RequestHandlerContext['actions']; + expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot( `"Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"` ); }); @@ -144,13 +150,17 @@ describe('Actions Plugin', () => { }; beforeEach(async () => { - setup = await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setup = await plugin.setup(coreSetup as any, pluginsSetup); }); it('should throw error when license type is invalid', async () => { expect(() => setup.registerType({ ...sampleActionType, + // we're faking an invalid value, this requires stripping the typing + // eslint-disable-next-line @typescript-eslint/no-explicit-any minimumLicenseRequired: 'foo' as any, }) ).toThrowErrorMatchingInlineSnapshot(`"\\"foo\\" is not a valid license type"`); @@ -211,7 +221,9 @@ describe('Actions Plugin', () => { describe('getActionsClientWithRequest()', () => { it('should not throw error when ESO plugin not using a generated key', async () => { - await plugin.setup(coreSetup, { + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, { ...pluginsSetup, encryptedSavedObjects: { ...pluginsSetup.encryptedSavedObjects, @@ -224,7 +236,9 @@ describe('Actions Plugin', () => { }); it('should throw error when ESO plugin using generated key', async () => { - await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, pluginsSetup); const pluginStart = plugin.start(coreStart, pluginsStart); expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index a8ab3bbb2fad2..280c14ca8c058 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -117,7 +117,10 @@ export class ActionsPlugin implements Plugin, Plugi this.preconfiguredActions = []; } - public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { + public async setup( + core: CoreSetup, + plugins: ActionsPluginsSetup + ): Promise { this.licenseState = new LicenseState(plugins.licensing.license$); this.isESOUsingEphemeralEncryptionKey = plugins.encryptedSavedObjects.usingEphemeralEncryptionKey; @@ -182,7 +185,7 @@ export class ActionsPlugin implements Plugin, Plugi const usageCollection = plugins.usageCollection; if (usageCollection) { - core.getStartServices().then(async ([, startPlugins]: [CoreStart, any, any]) => { + core.getStartServices().then(async ([, startPlugins]) => { registerActionsUsageCollector(usageCollection, startPlugins.taskManager); initializeActionsTelemetry( @@ -299,7 +302,7 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( defaultKibanaIndex: string - ): IContextProvider, 'actions'> => { + ): IContextProvider, 'actions'> => { const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, preconfiguredActions } = this; return async function actionsRouteHandlerContext(context, request) { diff --git a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts index ec525adb8eab6..06f65ff23106c 100644 --- a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts @@ -7,12 +7,17 @@ import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { identity } from 'lodash'; import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { ActionType } from '../../common'; +import { ActionsClientMock, actionsClientMock } from '../actions_client.mock'; export function mockHandlerArguments( - { actionsClient, listTypes: listTypesRes = [] }: any, - req: any, + { + actionsClient = actionsClientMock.create(), + listTypes: listTypesRes = [], + }: { actionsClient?: ActionsClientMock; listTypes?: ActionType[] }, + req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { const listTypes = jest.fn(() => listTypesRes); return [ ({ @@ -31,7 +36,7 @@ export function mockHandlerArguments( }, }, } as unknown) as RequestHandlerContext, - req as KibanaRequest, + req as KibanaRequest, mockResponseFactory(res), ]; } diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts index a01c5e9c2452c..1fa85c86e0651 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -8,6 +8,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -40,10 +41,11 @@ describe('createActionRoute', () => { name: 'My name', actionTypeId: 'abc', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - create: jest.fn().mockResolvedValueOnce(createResult), - }; + + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce(createResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -89,16 +91,16 @@ describe('createActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - const actionsClient = { - create: jest.fn().mockResolvedValueOnce({ - id: '1', - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }); - const [context, req, res] = mockHandlerArguments(actionsClient, {}); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); await handler(context, req, res); @@ -117,16 +119,16 @@ describe('createActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - const actionsClient = { - create: jest.fn().mockResolvedValueOnce({ - id: '1', - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }); - const [context, req, res] = mockHandlerArguments(actionsClient, {}); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); @@ -141,9 +143,8 @@ describe('createActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - const actionsClient = { - create: jest.fn().mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok', 'forbidden']); diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index 0cd3143dc48d1..7f239bda41a60 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -36,9 +36,9 @@ export const createActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any>, + req: KibanaRequest>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { diff --git a/x-pack/plugins/actions/server/routes/delete.test.ts b/x-pack/plugins/actions/server/routes/delete.test.ts index b0929f48d7875..e63989e27a57c 100644 --- a/x-pack/plugins/actions/server/routes/delete.test.ts +++ b/x-pack/plugins/actions/server/routes/delete.test.ts @@ -8,6 +8,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../mocks'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -35,9 +36,8 @@ describe('deleteActionRoute', () => { } `); - const actionsClient = { - delete: jest.fn().mockResolvedValueOnce({}), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -71,13 +71,15 @@ describe('deleteActionRoute', () => { const [, handler] = router.delete.mock.calls[0]; - const actionsClient = { - delete: jest.fn().mockResolvedValueOnce({}), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(actionsClient, { - params: { id: '1' }, - }); + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + } + ); await handler(context, req, res); @@ -96,13 +98,15 @@ describe('deleteActionRoute', () => { const [, handler] = router.delete.mock.calls[0]; - const actionsClient = { - delete: jest.fn().mockResolvedValueOnce({}), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(actionsClient, { - id: '1', - }); + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + id: '1', + } + ); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index ffd1f0faabbab..b76aa27fa5d9b 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -37,9 +37,9 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts index 52d0aa706e703..79d3c1dfb8d22 100644 --- a/x-pack/plugins/actions/server/routes/execute.ts +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -43,9 +43,9 @@ export const executeActionRoute = ( }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, TypeOf, any>, + req: KibanaRequest, unknown, TypeOf>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); const { params } = req.body; const { id } = req.params; diff --git a/x-pack/plugins/actions/server/routes/get.test.ts b/x-pack/plugins/actions/server/routes/get.test.ts index 7de4d93d91bb6..f701be579d99d 100644 --- a/x-pack/plugins/actions/server/routes/get.test.ts +++ b/x-pack/plugins/actions/server/routes/get.test.ts @@ -9,6 +9,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -41,10 +42,11 @@ describe('getActionRoute', () => { actionTypeId: '2', name: 'action name', config: {}, + isPreconfigured: false, }; - const actionsClient = { - get: jest.fn().mockResolvedValueOnce(getResult), - }; + + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce(getResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -60,6 +62,7 @@ describe('getActionRoute', () => { "actionTypeId": "2", "config": Object {}, "id": "1", + "isPreconfigured": false, "name": "action name", }, } @@ -81,17 +84,17 @@ describe('getActionRoute', () => { const [, handler] = router.get.mock.calls[0]; - const actionsClient = { - get: jest.fn().mockResolvedValueOnce({ - id: '1', - actionTypeId: '2', - name: 'action name', - config: {}, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }); const [context, req, res] = mockHandlerArguments( - actionsClient, + { actionsClient }, { params: { id: '1' }, }, @@ -115,17 +118,17 @@ describe('getActionRoute', () => { const [, handler] = router.get.mock.calls[0]; - const actionsClient = { - get: jest.fn().mockResolvedValueOnce({ - id: '1', - actionTypeId: '2', - name: 'action name', - config: {}, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }); const [context, req, res] = mockHandlerArguments( - actionsClient, + { actionsClient }, { params: { id: '1' }, }, diff --git a/x-pack/plugins/actions/server/routes/get.ts b/x-pack/plugins/actions/server/routes/get.ts index cd29e54556b02..30e4289ed30c8 100644 --- a/x-pack/plugins/actions/server/routes/get.ts +++ b/x-pack/plugins/actions/server/routes/get.ts @@ -32,9 +32,9 @@ export const getActionRoute = (router: IRouter, licenseState: ILicenseState) => }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/get_all.test.ts b/x-pack/plugins/actions/server/routes/get_all.test.ts index 1422bf0965786..e00054fd3746f 100644 --- a/x-pack/plugins/actions/server/routes/get_all.test.ts +++ b/x-pack/plugins/actions/server/routes/get_all.test.ts @@ -9,6 +9,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -36,9 +37,8 @@ describe('getAllActionRoute', () => { } `); - const actionsClient = { - getAll: jest.fn().mockResolvedValueOnce([]), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.mockResolvedValueOnce([]); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); @@ -72,9 +72,8 @@ describe('getAllActionRoute', () => { } `); - const actionsClient = { - getAll: jest.fn().mockResolvedValueOnce([]), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.mockResolvedValueOnce([]); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); @@ -104,9 +103,8 @@ describe('getAllActionRoute', () => { } `); - const actionsClient = { - getAll: jest.fn().mockResolvedValueOnce([]), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.mockResolvedValueOnce([]); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts index c70a13bc01c9f..f6745427f74c7 100644 --- a/x-pack/plugins/actions/server/routes/get_all.ts +++ b/x-pack/plugins/actions/server/routes/get_all.ts @@ -25,9 +25,9 @@ export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/list_action_types.test.ts b/x-pack/plugins/actions/server/routes/list_action_types.test.ts index 38410f45f091d..205752d5a49d1 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.test.ts +++ b/x-pack/plugins/actions/server/routes/list_action_types.test.ts @@ -9,6 +9,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { LicenseType } from '../../../../plugins/licensing/server'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -41,6 +42,9 @@ describe('listActionTypesRoute', () => { id: '1', name: 'name', enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, }, ]; @@ -51,7 +55,10 @@ describe('listActionTypesRoute', () => { "body": Array [ Object { "enabled": true, + "enabledInConfig": true, + "enabledInLicense": true, "id": "1", + "minimumLicenseRequired": "gold", "name": "name", }, ], @@ -87,6 +94,9 @@ describe('listActionTypesRoute', () => { id: '1', name: 'name', enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, }, ]; @@ -129,6 +139,9 @@ describe('listActionTypesRoute', () => { id: '1', name: 'name', enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, }, ]; diff --git a/x-pack/plugins/actions/server/routes/list_action_types.ts b/x-pack/plugins/actions/server/routes/list_action_types.ts index 71dcbd2e19bf7..50bc7405f4d6b 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.ts +++ b/x-pack/plugins/actions/server/routes/list_action_types.ts @@ -25,9 +25,9 @@ export const listActionTypesRoute = (router: IRouter, licenseState: ILicenseStat }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/update.test.ts b/x-pack/plugins/actions/server/routes/update.test.ts index 0c2e9b622ee57..6459a34bf0737 100644 --- a/x-pack/plugins/actions/server/routes/update.test.ts +++ b/x-pack/plugins/actions/server/routes/update.test.ts @@ -8,6 +8,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -40,11 +41,11 @@ describe('updateActionRoute', () => { actionTypeId: 'my-action-type-id', name: 'My name', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - update: jest.fn().mockResolvedValueOnce(updateResult), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -97,11 +98,11 @@ describe('updateActionRoute', () => { actionTypeId: 'my-action-type-id', name: 'My name', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - update: jest.fn().mockResolvedValueOnce(updateResult), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -140,11 +141,11 @@ describe('updateActionRoute', () => { actionTypeId: 'my-action-type-id', name: 'My name', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - update: jest.fn().mockResolvedValueOnce(updateResult), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -174,9 +175,8 @@ describe('updateActionRoute', () => { const [, handler] = router.put.mock.calls[0]; - const actionsClient = { - update: jest.fn().mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ actionsClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts index 263df678f293d..45ced77be922e 100644 --- a/x-pack/plugins/actions/server/routes/update.ts +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -39,9 +39,9 @@ export const updateActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, TypeOf, any>, + req: KibanaRequest, unknown, TypeOf>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 088398b40830e..9dcfdb81f5ebb 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -4,20 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, SavedObjectAttributes } from '../../../../src/core/server'; +import { + SavedObjectsClientContract, + SavedObjectAttributes, + KibanaRequest, +} from '../../../../src/core/server'; import { ActionTypeRegistry } from './action_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { ActionsClient } from './actions_client'; import { LicenseType } from '../../licensing/common/types'; export type WithoutQueryAndParams = Pick>; -export type GetServicesFunction = (request: any) => Services; +export type GetServicesFunction = (request: KibanaRequest) => Services; export type ActionTypeRegistryContract = PublicMethodsOf; export type GetBasePathFunction = (spaceId?: string) => string; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; export interface Services { - callCluster(path: string, opts: any): Promise; + callCluster(path: string, opts: unknown): Promise; savedObjectsClient: SavedObjectsClientContract; } @@ -45,8 +49,12 @@ export interface ActionsConfigType { export interface ActionTypeExecutorOptions { actionId: string; services: Services; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any config: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any secrets: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; } @@ -54,11 +62,15 @@ export interface ActionResult { id: string; actionTypeId: string; name: string; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any config?: Record; isPreconfigured: boolean; } export interface PreConfiguredAction extends ActionResult { + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any secrets: Record; } @@ -72,6 +84,8 @@ export interface ActionTypeExecutorResult { status: 'ok' | 'error'; message?: string; serviceMessage?: string; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; retry?: null | boolean | Date; } @@ -82,7 +96,7 @@ export type ExecutorType = ( ) => Promise; interface ValidatorType { - validate(value: any): any; + validate(value: unknown): Record; } export type ActionTypeCreator = (config?: ActionsConfigType) => ActionType; @@ -108,6 +122,13 @@ export interface RawAction extends SavedObjectAttributes { export interface ActionTaskParams extends SavedObjectAttributes { actionId: string; + // Saved Objects won't allow us to enforce unknown rather than any + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; apiKey?: string; } + +export interface ActionTaskExecutorParams { + spaceId: string; + actionTaskParamsId: string; +} diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index eabb38e61d17d..6996ba629f8da 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -55,6 +55,8 @@ export async function getTotalCount(callCluster: APICaller, kibanaIndex: string) 0 ), countByType: Object.keys(searchResult.aggregations.byActionTypeId.value.types).reduce( + // ES DSL aggregations are returned as `any` by callCluster + // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, [key.replace('.', '__')]: searchResult.aggregations.byActionTypeId.value.types[key], diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerting/common/alert.ts index 8f28c8fbaed7f..b410b6aa0187e 100644 --- a/x-pack/plugins/alerting/common/alert.ts +++ b/x-pack/plugins/alerting/common/alert.ts @@ -28,6 +28,8 @@ export interface Alert { consumer: string; schedule: IntervalSchedule; actions: AlertAction[]; + // This will have to remain `any` until we can extend Alert Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; scheduledTaskId?: string; createdBy: string | null; diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerting/server/alert_type_registry.test.ts index b51286281571e..f9df390242cd4 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.test.ts @@ -231,7 +231,7 @@ function alertTypeWithVariables(id: string, context: string, state: string): Ale name: `${id}-name`, actionGroups: [], defaultActionGroupId: id, - executor: (params: any): any => {}, + async executor() {}, }; if (!context && !state) { diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerting/server/alert_type_registry.ts index a2be43f9dacbd..55e39b6a817db 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.ts @@ -77,7 +77,7 @@ export class AlertTypeRegistry { } } -function normalizedActionVariables(actionVariables: any) { +function normalizedActionVariables(actionVariables: AlertType['actionVariables']) { return { context: actionVariables?.context ?? [], state: actionVariables?.state ?? [], diff --git a/x-pack/plugins/alerting/server/alerts_client.mock.ts b/x-pack/plugins/alerting/server/alerts_client.mock.ts index 3189fa214d5f7..1848b3432ae5a 100644 --- a/x-pack/plugins/alerting/server/alerts_client.mock.ts +++ b/x-pack/plugins/alerting/server/alerts_client.mock.ts @@ -7,9 +7,10 @@ import { AlertsClient } from './alerts_client'; type Schema = PublicMethodsOf; +export type AlertsClientMock = jest.Mocked; const createAlertsClientMock = () => { - const mocked: jest.Mocked = { + const mocked: AlertsClientMock = { create: jest.fn(), get: jest.fn(), getAlertState: jest.fn(), @@ -27,6 +28,8 @@ const createAlertsClientMock = () => { return mocked; }; -export const alertsClientMock = { +export const alertsClientMock: { + create: () => AlertsClientMock; +} = { create: createAlertsClientMock, }; diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client.test.ts index a9ff5ee8ecdc6..6c7b93aa64003 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client.test.ts @@ -5,7 +5,7 @@ */ import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; -import { AlertsClient } from './alerts_client'; +import { AlertsClient, CreateOptions } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; @@ -45,6 +45,7 @@ beforeEach(() => { }); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any (global as any).Date = class Date { constructor() { return mockedDate; @@ -54,7 +55,7 @@ const mockedDate = new Date('2019-02-12T21:01:22.479Z'); } }; -function getMockData(overwrites: Record = {}) { +function getMockData(overwrites: Record = {}): CreateOptions['data'] { return { enabled: true, name: 'abc', diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 3e4c26d3444c9..ff501055ba9fe 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -24,6 +24,7 @@ import { IntervalSchedule, SanitizedAlert, AlertTaskState, + RawAlertAction, } from './types'; import { validateAlertTypeParams } from './lib'; import { @@ -83,7 +84,7 @@ export interface FindResult { data: SanitizedAlert[]; } -interface CreateOptions { +export interface CreateOptions { data: Omit< Alert, | 'id' @@ -109,7 +110,7 @@ interface UpdateOptions { tags: string[]; schedule: IntervalSchedule; actions: NormalizedAlertAction[]; - params: Record; + params: Record; throttle: string | null; }; } @@ -172,7 +173,7 @@ export class AlertsClient { createdBy: username, updatedBy: username, createdAt: new Date().toISOString(), - params: validatedAlertTypeParams, + params: validatedAlertTypeParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], }; @@ -337,7 +338,7 @@ export class AlertsClient { ...attributes, ...data, ...apiKeyAttributes, - params: validatedAlertTypeParams, + params: validatedAlertTypeParams as RawAlert['params'], actions, updatedBy: username, }, @@ -667,7 +668,7 @@ export class AlertsClient { private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { - const actionMap = new Map(); + const actionMap = new Map(); // map preconfigured actions for (const alertAction of alertActions) { const action = this.preconfiguredActions.find( @@ -712,8 +713,8 @@ export class AlertsClient { // if action is a save object, than actionTypeId should be under attributes property // if action is a preconfigured, than actionTypeId is the action property const actionTypeId = actionIds.find(actionId => actionId === id) - ? actionMapValue.attributes.actionTypeId - : actionMapValue.actionTypeId; + ? (actionMapValue as SavedObject>).attributes.actionTypeId + : (actionMapValue as RawAlertAction).actionTypeId; return { ...alertAction, actionRef, diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index 951d18a33b35f..e5aa0a674eccf 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -11,16 +11,13 @@ import { taskManagerMock } from '../../../plugins/task_manager/server/task_manag import { KibanaRequest } from '../../../../src/core/server'; import { loggingServiceMock, savedObjectsClientMock } from '../../../../src/core/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { AuthenticatedUser } from '../../../plugins/security/public'; +import { securityMock } from '../../../plugins/security/server/mocks'; jest.mock('./alerts_client'); const savedObjectsClient = savedObjectsClientMock.create(); -const securityPluginSetup = { - authc: { - grantAPIKeyAsInternalUser: jest.fn(), - getCurrentUser: jest.fn(), - }, -}; +const securityPluginSetup = securityMock.createSetup(); const alertsClientFactoryParams: jest.Mocked = { logger: loggingServiceMock.create().get(), taskManager: taskManagerMock.start(), @@ -30,7 +27,7 @@ const alertsClientFactoryParams: jest.Mocked = { encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(), preconfiguredActions: [], }; -const fakeRequest: Request = { +const fakeRequest = ({ headers: {}, getBasePath: () => '', path: '/', @@ -44,7 +41,7 @@ const fakeRequest: Request = { }, }, getSavedObjectsClient: () => savedObjectsClient, -} as any; +} as unknown) as Request; beforeEach(() => { jest.resetAllMocks(); @@ -86,12 +83,14 @@ test('getUserName() returns a name when security is enabled', async () => { const factory = new AlertsClientFactory(); factory.initialize({ ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, + securityPluginSetup, }); factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient); const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; - securityPluginSetup.authc.getCurrentUser.mockReturnValueOnce({ username: 'bob' }); + securityPluginSetup.authc.getCurrentUser.mockReturnValueOnce(({ + username: 'bob', + } as unknown) as AuthenticatedUser); const userNameResult = await constructorCall.getUserName(); expect(userNameResult).toEqual('bob'); }); @@ -121,7 +120,7 @@ test('createAPIKey() returns an API key when security is enabled', async () => { const factory = new AlertsClientFactory(); factory.initialize({ ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, + securityPluginSetup, }); factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient); const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; @@ -129,11 +128,12 @@ test('createAPIKey() returns an API key when security is enabled', async () => { securityPluginSetup.authc.grantAPIKeyAsInternalUser.mockResolvedValueOnce({ api_key: '123', id: 'abc', + name: '', }); const createAPIKeyResult = await constructorCall.createAPIKey(); expect(createAPIKeyResult).toEqual({ apiKeysEnabled: true, - result: { api_key: '123', id: 'abc' }, + result: { api_key: '123', id: 'abc', name: '' }, }); }); @@ -141,7 +141,7 @@ test('createAPIKey() throws when security plugin createAPIKey throws an error', const factory = new AlertsClientFactory(); factory.initialize({ ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, + securityPluginSetup, }); factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient); const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; diff --git a/x-pack/plugins/alerting/server/constants/plugin.ts b/x-pack/plugins/alerting/server/constants/plugin.ts index 173aa50013b40..9c276ed1d75de 100644 --- a/x-pack/plugins/alerting/server/constants/plugin.ts +++ b/x-pack/plugins/alerting/server/constants/plugin.ts @@ -9,6 +9,8 @@ import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/const export const PLUGIN = { ID: 'alerting', MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + // all plugins seem to use getI18nName with any + // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => i18n.translate('xpack.alerting.appName', { defaultMessage: 'Alerting', diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerting/server/lib/license_state.test.ts index 9bbd619dc4868..cbab98a6311dd 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.test.ts @@ -9,10 +9,10 @@ import { LicenseState } from './license_state'; import { licensingMock } from '../../../../plugins/licensing/server/mocks'; describe('license_state', () => { - let getRawLicense: any; + const getRawLicense = jest.fn(); beforeEach(() => { - getRawLicense = jest.fn(); + jest.resetAllMocks(); }); describe('status is LICENSE_STATUS_INVALID', () => { diff --git a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts b/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts index 248d896c06ac2..b6dcb522f0925 100644 --- a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts +++ b/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts @@ -5,15 +5,15 @@ */ import Boom from 'boom'; -import { AlertType } from '../types'; +import { AlertType, AlertExecutorOptions } from '../types'; -export function validateAlertTypeParams>( +export function validateAlertTypeParams( alertType: AlertType, - params: T -): T { + params: Record +): AlertExecutorOptions['params'] { const validator = alertType.validate && alertType.validate.params; if (!validator) { - return params; + return params as AlertExecutorOptions['params']; } try { diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 74a1f2349180e..267e68930a5d7 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertingPlugin } from './plugin'; +import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../../plugins/licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; +import { KibanaRequest, CoreSetup } from 'kibana/server'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -20,19 +21,19 @@ describe('Alerting Plugin', () => { const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); await plugin.setup( - { + ({ ...coreSetup, http: { ...coreSetup.http, route: jest.fn(), }, - } as any, - { + } as unknown) as CoreSetup, + ({ licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), eventLog: eventLogServiceMock.create(), - } as any + } as unknown) as AlertingPluginsSetup ); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); @@ -58,34 +59,34 @@ describe('Alerting Plugin', () => { const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); await plugin.setup( - { + ({ ...coreSetup, http: { ...coreSetup.http, route: jest.fn(), }, - } as any, - { + } as unknown) as CoreSetup, + ({ licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), eventLog: eventLogServiceMock.create(), - } as any + } as unknown) as AlertingPluginsSetup ); const startContract = plugin.start( - coreMock.createStart() as any, - { + coreMock.createStart() as ReturnType, + ({ actions: { execute: jest.fn(), getActionsClientWithRequest: jest.fn(), }, - } as any + } as unknown) as AlertingPluginsStart ); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(() => - startContract.getAlertsClientWithRequest({} as any) + startContract.getAlertsClientWithRequest({} as KibanaRequest) ).toThrowErrorMatchingInlineSnapshot( `"Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"` ); @@ -101,33 +102,33 @@ describe('Alerting Plugin', () => { usingEphemeralEncryptionKey: false, }; await plugin.setup( - { + ({ ...coreSetup, http: { ...coreSetup.http, route: jest.fn(), }, - } as any, - { + } as unknown) as CoreSetup, + ({ licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), eventLog: eventLogServiceMock.create(), - } as any + } as unknown) as AlertingPluginsSetup ); const startContract = plugin.start( - coreMock.createStart() as any, - { + coreMock.createStart() as ReturnType, + ({ actions: { execute: jest.fn(), getActionsClientWithRequest: jest.fn(), }, spaces: () => null, - } as any + } as unknown) as AlertingPluginsStart ); - const fakeRequest = { + const fakeRequest = ({ headers: {}, getBasePath: () => '', path: '/', @@ -141,8 +142,8 @@ describe('Alerting Plugin', () => { }, }, getSavedObjectsClient: jest.fn(), - }; - await startContract.getAlertsClientWithRequest(fakeRequest as any); + } as unknown) as KibanaRequest; + await startContract.getAlertsClientWithRequest(fakeRequest); }); }); }); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index ad39d09bd6d3d..35ebafce9dc67 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -117,7 +117,10 @@ export class AlertingPlugin { .toPromise(); } - public async setup(core: CoreSetup, plugins: AlertingPluginsSetup): Promise { + public async setup( + core: CoreSetup, + plugins: AlertingPluginsSetup + ): Promise { this.licenseState = new LicenseState(plugins.licensing.license$); this.spaces = plugins.spaces?.spacesService; this.security = plugins.security; @@ -157,7 +160,7 @@ export class AlertingPlugin { const usageCollection = plugins.usageCollection; if (usageCollection) { - core.getStartServices().then(async ([, startPlugins]: [CoreStart, any, any]) => { + core.getStartServices().then(async ([, startPlugins]) => { registerAlertsUsageCollector(usageCollection, startPlugins.taskManager); initializeAlertingTelemetry( @@ -246,7 +249,7 @@ export class AlertingPlugin { } private createRouteHandlerContext = (): IContextProvider< - RequestHandler, + RequestHandler, 'alerting' > => { const { alertTypeRegistry, alertsClientFactory } = this; diff --git a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts index 5a1d680eb06f3..6fb3df8446f5c 100644 --- a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts @@ -4,16 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IClusterClient, +} from 'kibana/server'; import { identity } from 'lodash'; import { httpServerMock } from '../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../alerts_client.mock'; +import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock'; +import { AlertType } from '../../common'; +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; export function mockHandlerArguments( - { alertsClient, listTypes: listTypesRes = [], elasticsearch }: any, - req: any, + { + alertsClient = alertsClientMock.create(), + listTypes: listTypesRes = [], + elasticsearch = elasticsearchServiceMock.createSetup(), + }: { + alertsClient?: AlertsClientMock; + listTypes?: AlertType[]; + elasticsearch?: jest.Mocked<{ + adminClient: jest.Mocked; + }>; + }, + req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { const listTypes = jest.fn(() => listTypesRes); return [ ({ @@ -25,7 +42,7 @@ export function mockHandlerArguments( }, }, } as unknown) as RequestHandlerContext, - req as KibanaRequest, + req as KibanaRequest, mockResponseFactory(res), ]; } diff --git a/x-pack/plugins/alerting/server/routes/create.test.ts b/x-pack/plugins/alerting/server/routes/create.test.ts index 294dd7e002d71..a4910495c8a40 100644 --- a/x-pack/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/create.test.ts @@ -142,7 +142,7 @@ describe('createAlertRoute', () => { alertsClient.create.mockResolvedValueOnce(createResult); - const [context, req, res] = mockHandlerArguments(alertsClient, {}); + const [context, req, res] = mockHandlerArguments({ alertsClient }, {}); await handler(context, req, res); @@ -163,7 +163,7 @@ describe('createAlertRoute', () => { alertsClient.create.mockResolvedValueOnce(createResult); - const [context, req, res] = mockHandlerArguments(alertsClient, {}); + const [context, req, res] = mockHandlerArguments({ alertsClient }, {}); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); diff --git a/x-pack/plugins/alerting/server/routes/create.ts b/x-pack/plugins/alerting/server/routes/create.ts index f08460ffcb453..0c038b6490483 100644 --- a/x-pack/plugins/alerting/server/routes/create.ts +++ b/x-pack/plugins/alerting/server/routes/create.ts @@ -54,9 +54,9 @@ export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any>, + req: KibanaRequest>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { diff --git a/x-pack/plugins/alerting/server/routes/delete.test.ts b/x-pack/plugins/alerting/server/routes/delete.test.ts index d6afdbd85e77a..416628d015b5a 100644 --- a/x-pack/plugins/alerting/server/routes/delete.test.ts +++ b/x-pack/plugins/alerting/server/routes/delete.test.ts @@ -74,9 +74,12 @@ describe('deleteAlertRoute', () => { alertsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(alertsClient, { - params: { id: '1' }, - }); + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + } + ); await handler(context, req, res); @@ -97,9 +100,12 @@ describe('deleteAlertRoute', () => { alertsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(alertsClient, { - id: '1', - }); + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + id: '1', + } + ); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); diff --git a/x-pack/plugins/alerting/server/routes/delete.ts b/x-pack/plugins/alerting/server/routes/delete.ts index 8d77c9b395e59..7f6600b1ec48e 100644 --- a/x-pack/plugins/alerting/server/routes/delete.ts +++ b/x-pack/plugins/alerting/server/routes/delete.ts @@ -33,9 +33,9 @@ export const deleteAlertRoute = (router: IRouter, licenseState: LicenseState) => }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/disable.ts b/x-pack/plugins/alerting/server/routes/disable.ts index fcc7116a697b1..c7e7b1001f82d 100644 --- a/x-pack/plugins/alerting/server/routes/disable.ts +++ b/x-pack/plugins/alerting/server/routes/disable.ts @@ -33,9 +33,9 @@ export const disableAlertRoute = (router: IRouter, licenseState: LicenseState) = }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/enable.ts b/x-pack/plugins/alerting/server/routes/enable.ts index 2283ae4a4c765..3ed4fb0739d3d 100644 --- a/x-pack/plugins/alerting/server/routes/enable.ts +++ b/x-pack/plugins/alerting/server/routes/enable.ts @@ -35,9 +35,9 @@ export const enableAlertRoute = (router: IRouter, licenseState: LicenseState) => handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/find.test.ts b/x-pack/plugins/alerting/server/routes/find.test.ts index 879d9498cda03..cc601bd42b8ca 100644 --- a/x-pack/plugins/alerting/server/routes/find.test.ts +++ b/x-pack/plugins/alerting/server/routes/find.test.ts @@ -108,13 +108,16 @@ describe('findAlertRoute', () => { data: [], }); - const [context, req, res] = mockHandlerArguments(alertsClient, { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }); + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + } + ); await handler(context, req, res); diff --git a/x-pack/plugins/alerting/server/routes/find.ts b/x-pack/plugins/alerting/server/routes/find.ts index 0787f5c6b5ad6..c723419a965c5 100644 --- a/x-pack/plugins/alerting/server/routes/find.ts +++ b/x-pack/plugins/alerting/server/routes/find.ts @@ -55,9 +55,9 @@ export const findAlertRoute = (router: IRouter, licenseState: LicenseState) => { }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any>, + req: KibanaRequest, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/get.test.ts b/x-pack/plugins/alerting/server/routes/get.test.ts index fe89c86edc2b1..7335f13c85a4d 100644 --- a/x-pack/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/get.test.ts @@ -99,7 +99,7 @@ describe('getAlertRoute', () => { alertsClient.get.mockResolvedValueOnce(mockedAlert); const [context, req, res] = mockHandlerArguments( - alertsClient, + { alertsClient }, { params: { id: '1' }, }, @@ -126,7 +126,7 @@ describe('getAlertRoute', () => { alertsClient.get.mockResolvedValueOnce(mockedAlert); const [context, req, res] = mockHandlerArguments( - alertsClient, + { alertsClient }, { params: { id: '1' }, }, diff --git a/x-pack/plugins/alerting/server/routes/get.ts b/x-pack/plugins/alerting/server/routes/get.ts index 39fbe64b62182..6d652d1304f65 100644 --- a/x-pack/plugins/alerting/server/routes/get.ts +++ b/x-pack/plugins/alerting/server/routes/get.ts @@ -33,9 +33,9 @@ export const getAlertRoute = (router: IRouter, licenseState: LicenseState) => { }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.ts b/x-pack/plugins/alerting/server/routes/get_alert_state.ts index c5493c4abf57a..552bfea22a42b 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.ts +++ b/x-pack/plugins/alerting/server/routes/get_alert_state.ts @@ -33,9 +33,9 @@ export const getAlertStateRoute = (router: IRouter, licenseState: LicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index fa2358a1f181c..bfdbc95a7d2da 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -39,9 +39,9 @@ export function healthRoute( }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); try { const { diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts b/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts index cc83d10fb97fd..37b52f1ec7923 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts @@ -47,6 +47,7 @@ describe('listAlertTypesRoute', () => { }, ], defaultActionGroupId: 'default', + actionVariables: [], }, ]; @@ -62,6 +63,7 @@ describe('listAlertTypesRoute', () => { "name": "Default", }, ], + "actionVariables": Array [], "defaultActionGroupId": "default", "id": "1", "name": "name", @@ -99,6 +101,14 @@ describe('listAlertTypesRoute', () => { id: '1', name: 'name', enabled: true, + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + actionVariables: [], }, ]; @@ -147,6 +157,7 @@ describe('listAlertTypesRoute', () => { }, ], defaultActionGroupId: 'default', + actionVariables: [], }, ]; diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.ts b/x-pack/plugins/alerting/server/routes/list_alert_types.ts index 455bc5e378b6d..7ab64cf932051 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.ts +++ b/x-pack/plugins/alerting/server/routes/list_alert_types.ts @@ -26,9 +26,9 @@ export const listAlertTypesRoute = (router: IRouter, licenseState: LicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/mute_all.ts b/x-pack/plugins/alerting/server/routes/mute_all.ts index 29ef7d6b6b03b..d1b4322bd1ccb 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.ts +++ b/x-pack/plugins/alerting/server/routes/mute_all.ts @@ -33,9 +33,9 @@ export const muteAllAlertRoute = (router: IRouter, licenseState: LicenseState) = }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.ts b/x-pack/plugins/alerting/server/routes/mute_instance.ts index 7a071b1535dc7..fbdda62836d74 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/mute_instance.ts @@ -34,9 +34,9 @@ export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseSta }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.ts b/x-pack/plugins/alerting/server/routes/unmute_all.ts index 81e28a81874cd..e09f2fe6b8b93 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_all.ts @@ -33,9 +33,9 @@ export const unmuteAllAlertRoute = (router: IRouter, licenseState: LicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.ts b/x-pack/plugins/alerting/server/routes/unmute_instance.ts index de081ae7f1fcb..64ba22dc3ea0b 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_instance.ts @@ -34,9 +34,9 @@ export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: LicenseS }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/update.ts b/x-pack/plugins/alerting/server/routes/update.ts index 45f7b26b521d4..7f07749311598 100644 --- a/x-pack/plugins/alerting/server/routes/update.ts +++ b/x-pack/plugins/alerting/server/routes/update.ts @@ -56,9 +56,9 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, TypeOf, any>, + req: KibanaRequest, unknown, TypeOf>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.ts b/x-pack/plugins/alerting/server/routes/update_api_key.ts index f70d30f0bb5da..9d0c34fc1a015 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.ts +++ b/x-pack/plugins/alerting/server/routes/update_api_key.ts @@ -35,9 +35,9 @@ export const updateApiKeyRoute = (router: IRouter, licenseState: LicenseState) = handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts index 9f5e3851aa29d..4be506b78493b 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts +++ b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts @@ -7,10 +7,17 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; -import { SanitizedAlert, AlertTaskState, alertParamsSchema, alertStateSchema } from '../../common'; +import { + SanitizedAlert, + AlertTaskState, + alertParamsSchema, + alertStateSchema, + AlertTaskParams, +} from '../../common'; export interface AlertTaskInstance extends ConcreteTaskInstance { state: AlertTaskState; + params: AlertTaskParams; } const enumerateErrorFields = (e: t.Errors) => diff --git a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts b/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts index 1c4d8a42d2830..f5914fdf01a16 100644 --- a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts @@ -7,6 +7,7 @@ import { getNextRunAt } from './get_next_run_at'; const mockedNow = new Date('2019-06-03T18:55:25.982Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any (global as any).Date = class Date extends global.Date { static now() { return mockedNow.getTime(); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 1d4b12e96bc76..9c8cf4b1c968d 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -5,7 +5,7 @@ */ import { pick, mapValues, omit, without } from 'lodash'; -import { Logger, SavedObject } from '../../../../../src/core/server'; +import { Logger, SavedObject, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; import { createExecutionHandler } from './create_execution_handler'; @@ -93,7 +93,7 @@ export class TaskRunner { }, }; - return this.context.getServices(fakeRequest); + return this.context.getServices((fakeRequest as unknown) as KibanaRequest); } private getExecutionHandler( @@ -178,7 +178,7 @@ export class TaskRunner { }; eventLogger.startTiming(event); - let updatedAlertTypeState: void | Record; + let updatedAlertTypeState: void | Record; try { updatedAlertTypeState = await this.alertType.executor({ alertId, diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index e0cff58c4d40a..64f846d13c0bf 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -29,7 +29,7 @@ export function transformActionParams({ actionParams, state, }: TransformActionParamsOptions): AlertActionParams { - const result = cloneDeep(actionParams, (value: any) => { + const result = cloneDeep(actionParams, (value: unknown) => { if (!isString(value)) return; // when the list of variables we pass in here changes, diff --git a/x-pack/plugins/alerting/server/test_utils/index.ts b/x-pack/plugins/alerting/server/test_utils/index.ts index be9c5493ccf2b..e8089984a786a 100644 --- a/x-pack/plugins/alerting/server/test_utils/index.ts +++ b/x-pack/plugins/alerting/server/test_utils/index.ts @@ -5,7 +5,7 @@ */ interface Resolvable { - resolve: (arg?: T) => void; + resolve: (arg: T) => void; } /** @@ -13,12 +13,10 @@ interface Resolvable { * coordinating async tests. */ export function resolvable(): Promise & Resolvable { - let resolve: (arg?: T) => void; - const result = new Promise(r => { - resolve = r; - }) as any; - - result.resolve = (arg: T) => resolve(arg); - - return result; + let resolve: (arg: T) => void; + return Object.assign(new Promise(r => (resolve = r)), { + resolve(arg: T) { + return setTimeout(() => resolve(arg), 0); + }, + }); } diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 739a0d0aece24..bc98cae65b4e6 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -7,15 +7,22 @@ import { AlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; -import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../src/core/server'; +import { + SavedObjectAttributes, + SavedObjectsClientContract, + KibanaRequest, +} from '../../../../src/core/server'; import { Alert, AlertActionParams, ActionGroup } from '../common'; import { AlertsClient } from './alerts_client'; export * from '../common'; +// This will have to remain `any` until we can extend Alert Executors with generics +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type State = Record; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type Context = Record; export type WithoutQueryAndParams = Pick>; -export type GetServicesFunction = (request: any) => Services; +export type GetServicesFunction = (request: KibanaRequest) => Services; export type GetBasePathFunction = (spaceId?: string) => string; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; @@ -29,6 +36,8 @@ declare module 'src/core/server' { } export interface Services { + // This will have to remain `any` until we can extend Alert Services with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any callCluster(path: string, opts: any): Promise; savedObjectsClient: SavedObjectsClientContract; } @@ -42,6 +51,8 @@ export interface AlertExecutorOptions { startedAt: Date; previousStartedAt: Date | null; services: AlertServices; + // This will have to remain `any` until we can extend Alert Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; state: State; spaceId: string; @@ -61,7 +72,7 @@ export interface AlertType { id: string; name: string; validate?: { - params?: { validate: (object: any) => any }; + params?: { validate: (object: unknown) => AlertExecutorOptions['params'] }; }; actionGroups: ActionGroup[]; defaultActionGroupId: ActionGroup['id']; diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 9c710fa3b3b8e..2edf1c1061864 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -5,6 +5,7 @@ */ import { APICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; const alertTypeMetric = { scripted_metric: { @@ -246,6 +247,8 @@ export async function getTotalCountAggregations(callCluster: APICaller, kibanaIn return { count_total: totalAlertsCount, count_by_type: Object.keys(results.aggregations.byAlertTypeId.value.types).reduce( + // ES DSL aggregations are returned as `any` by callCluster + // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, [key.replace('.', '__')]: results.aggregations.byAlertTypeId.value.types[key], @@ -284,7 +287,7 @@ export async function getTotalCountAggregations(callCluster: APICaller, kibanaIn } export async function getTotalCountInUse(callCluster: APICaller, kibanaInex: string) { - const searchResult = await callCluster('search', { + const searchResult: SearchResponse = await callCluster('search', { index: kibanaInex, rest_total_hits_as_int: true, body: { @@ -305,6 +308,8 @@ export async function getTotalCountInUse(callCluster: APICaller, kibanaInex: str 0 ), countByType: Object.keys(searchResult.aggregations.byAlertTypeId.value.types).reduce( + // ES DSL aggregations are returned as `any` by callCluster + // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, [key.replace('.', '__')]: searchResult.aggregations.byAlertTypeId.value.types[key], diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts index 33d1e1897e943..c209894fb6e89 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts @@ -6,6 +6,7 @@ import { ParamsSchema, Params } from './alert_type_params'; import { runTests } from './lib/core_query_types.test'; +import { TypeOf } from '@kbn/config-schema'; const DefaultParams: Writable> = { index: 'index-name', @@ -21,6 +22,7 @@ const DefaultParams: Writable> = { describe('alertType Params validate()', () => { runTests(ParamsSchema, DefaultParams); + // eslint-disable-next-line @typescript-eslint/no-explicit-any let params: any; beforeEach(() => { params = { ...DefaultParams }; @@ -64,7 +66,7 @@ describe('alertType Params validate()', () => { return () => validate(); } - function validate(): any { + function validate(): TypeOf { return ParamsSchema.validate(params); } }); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts index f83d7fa07cd2a..4a822156ebd06 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts @@ -30,12 +30,12 @@ export const ParamsSchema = schema.object( const betweenComparators = new Set(['between', 'notBetween']); // using direct type not allowed, circular reference, so body is typed to any -function validateParams(anyParams: any): string | undefined { +function validateParams(anyParams: unknown): string | undefined { // validate core query parts, return if it fails validation (returning string) const coreQueryValidated = validateCoreQueryBody(anyParams); if (coreQueryValidated) return coreQueryValidated; - const { thresholdComparator, threshold }: Params = anyParams; + const { thresholdComparator, threshold }: Params = anyParams as Params; if (betweenComparators.has(thresholdComparator) && threshold.length === 1) { return i18n.translate('xpack.alertingBuiltins.indexThreshold.invalidThreshold2ErrorMessage', { diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts index 109785b835bdf..6c9c3542aea03 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts @@ -19,7 +19,8 @@ const DefaultParams: Writable> = { timeWindowUnit: 'm', }; -export function runTests(schema: ObjectType, defaultTypeParams: Record): void { +export function runTests(schema: ObjectType, defaultTypeParams: Record): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any let params: any; describe('coreQueryTypes', () => { @@ -186,7 +187,7 @@ export function runTests(schema: ObjectType, defaultTypeParams: Record validate(); } - function validate(): any { + function validate(): unknown { return schema.validate(params); } } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts index 6e9c0072bf7b6..c8da61fb56d21 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts @@ -41,9 +41,15 @@ export type CoreQueryParams = TypeOf; // Meant to be used in a "subclass"'s schema body validator, so the // anyParams object is assumed to have been validated with the schema // above. -// Using direct type not allowed, circular reference, so body is typed to any. -export function validateCoreQueryBody(anyParams: any): string | undefined { - const { aggType, aggField, groupBy, termField, termSize }: CoreQueryParams = anyParams; +// Using direct type not allowed, circular reference, so body is typed to unknown. +export function validateCoreQueryBody(anyParams: unknown): string | undefined { + const { + aggType, + aggField, + groupBy, + termField, + termSize, + }: CoreQueryParams = anyParams as CoreQueryParams; if (aggType !== 'count' && !aggField) { return i18n.translate('xpack.alertingBuiltins.indexThreshold.aggTypeRequiredErrorMessage', { defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"', diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts index 1d9cc1c98bc01..9c4133be6f483 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchResponse } from 'elasticsearch'; import { DEFAULT_GROUPS } from '../index'; import { getDateRangeInfo } from './date_range_info'; import { Logger, CallCluster } from '../../../types'; @@ -35,6 +36,8 @@ export async function timeSeriesQuery( const dateRangeInfo = getDateRangeInfo({ dateStart, dateEnd, window, interval }); // core query + // Constructing a typesafe ES query in JS is problematic, use any escapehatch for now + // eslint-disable-next-line @typescript-eslint/no-explicit-any const esQuery: any = { index, body: { @@ -122,7 +125,7 @@ export async function timeSeriesQuery( }; } - let esResult: any; + let esResult: SearchResponse; const logPrefix = 'indexThreshold timeSeriesQuery: callCluster'; logger.debug(`${logPrefix} call: ${JSON.stringify(esQuery)}`); @@ -147,7 +150,7 @@ export async function timeSeriesQuery( function getResultFromEs( isCountAgg: boolean, isGroupAgg: boolean, - esResult: Record + esResult: SearchResponse ): TimeSeriesResult { const aggregations = esResult?.aggregations || {}; diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts index fcbd49b26ffd0..ec164122032cb 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts @@ -6,6 +6,7 @@ import { TimeSeriesQuerySchema, TimeSeriesQuery } from './time_series_types'; import { runTests } from './core_query_types.test'; +import { TypeOf } from '@kbn/config-schema'; const DefaultParams: Writable> = { index: 'index-name', @@ -19,6 +20,7 @@ const DefaultParams: Writable> = { describe('TimeSeriesParams validate()', () => { runTests(TimeSeriesQuerySchema, DefaultParams); + // eslint-disable-next-line @typescript-eslint/no-explicit-any let params: any; beforeEach(() => { params = { ...DefaultParams }; @@ -102,7 +104,7 @@ describe('TimeSeriesParams validate()', () => { return () => validate(); } - function validate(): any { + function validate(): TypeOf { return TimeSeriesQuerySchema.validate(params); } }); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts index abe5d562027eb..40e6f187ce18f 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts @@ -48,13 +48,13 @@ export const TimeSeriesQuerySchema = schema.object( } ); -// using direct type not allowed, circular reference, so body is typed to any -function validateBody(anyParams: any): string | undefined { +// using direct type not allowed, circular reference, so body is typed to unknown +function validateBody(anyParams: unknown): string | undefined { // validate core query parts, return if it fails validation (returning string) const coreQueryValidated = validateCoreQueryBody(anyParams); if (coreQueryValidated) return coreQueryValidated; - const { dateStart, dateEnd, interval }: TimeSeriesQuery = anyParams; + const { dateStart, dateEnd, interval } = anyParams as TimeSeriesQuery; // dates already validated in validateDate(), if provided const epochStart = dateStart ? Date.parse(dateStart) : undefined; diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts index 32d6409d9c9fb..5cc41671f6167 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts @@ -38,7 +38,7 @@ export function createFieldsRoute(service: Service, router: IRouter, baseRoute: ); async function handler( ctx: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory ): Promise { service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts index c08450448b44c..ebcf6b4f0e45a 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts @@ -18,6 +18,7 @@ import { KibanaResponseFactory, IScopedClusterClient, } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { Service } from '../../../types'; const bodySchema = schema.object({ @@ -40,7 +41,7 @@ export function createIndicesRoute(service: Service, router: IRouter, baseRoute: ); async function handler( ctx: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory ): Promise { const pattern = req.body.pattern; @@ -102,12 +103,14 @@ async function getIndicesFromPattern( }, }, }; - const response = await dataClient.callAsCurrentUser('search', params); - if (response.status === 404 || !response.aggregations) { + const response: SearchResponse = await dataClient.callAsCurrentUser('search', params); + // TODO: Investigate when the status field might appear here, type suggests it shouldn't ever happen + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((response as any).status === 404 || !response.aggregations) { return []; } - return response.aggregations.indices.buckets.map((bucket: any) => bucket.key); + return (response.aggregations as IndiciesAggregation).indices.buckets.map(bucket => bucket.key); } async function getAliasesFromPattern( @@ -137,3 +140,9 @@ async function getAliasesFromPattern( return result; } + +interface IndiciesAggregation { + indices: { + buckets: Array<{ key: string }>; + }; +} diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts index c8129c2428ee4..201c82060f386 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts @@ -30,7 +30,7 @@ export function createTimeSeriesQueryRoute(service: Service, router: IRouter, ba ); async function handler( ctx: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory ): Promise { service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index 986486902c3fa..470123ada48ea 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -42,6 +42,8 @@ describe('indexDocument', () => { }); describe('doesIlmPolicyExist', () => { + // ElasticsearchError can be a bit random in shape, we need an any here + // eslint-disable-next-line @typescript-eslint/no-explicit-any const notFoundError = new Error('Not found') as any; notFoundError.statusCode = 404; @@ -187,6 +189,8 @@ describe('createIndex', () => { }); test(`shouldn't throw when an error of type resource_already_exists_exception is thrown`, async () => { + // ElasticsearchError can be a bit random in shape, we need an any here + // eslint-disable-next-line @typescript-eslint/no-explicit-any const err = new Error('Already exists') as any; err.body = { error: { diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 409bb2d00e161..6d5c6b31a637c 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -5,6 +5,7 @@ */ import { reject, isUndefined } from 'lodash'; +import { SearchResponse, Client } from 'elasticsearch'; import { Logger, ClusterClient } from '../../../../../src/core/server'; import { IEvent } from '../types'; import { FindOptionsType } from '../event_log_client'; @@ -33,8 +34,8 @@ export class ClusterClientAdapter { this.clusterClientPromise = opts.clusterClientPromise; } - public async indexDocument(doc: any): Promise { - await this.callEs('index', doc); + public async indexDocument(doc: unknown): Promise { + await this.callEs>('index', doc); } public async doesIlmPolicyExist(policyName: string): Promise { @@ -51,7 +52,7 @@ export class ClusterClientAdapter { return true; } - public async createIlmPolicy(policyName: string, policy: any): Promise { + public async createIlmPolicy(policyName: string, policy: unknown): Promise { const request = { method: 'PUT', path: `_ilm/policy/${policyName}`, @@ -67,21 +68,27 @@ export class ClusterClientAdapter { public async doesIndexTemplateExist(name: string): Promise { let result; try { - result = await this.callEs('indices.existsTemplate', { name }); + result = await this.callEs>( + 'indices.existsTemplate', + { name } + ); } catch (err) { throw new Error(`error checking existance of index template: ${err.message}`); } return result as boolean; } - public async createIndexTemplate(name: string, template: any): Promise { + public async createIndexTemplate(name: string, template: unknown): Promise { const addTemplateParams = { name, create: true, body: template, }; try { - await this.callEs('indices.putTemplate', addTemplateParams); + await this.callEs>( + 'indices.putTemplate', + addTemplateParams + ); } catch (err) { // The error message doesn't have a type attribute we can look to guarantee it's due // to the template already existing (only long message) so we'll check ourselves to see @@ -97,16 +104,19 @@ export class ClusterClientAdapter { public async doesAliasExist(name: string): Promise { let result; try { - result = await this.callEs('indices.existsAlias', { name }); + result = await this.callEs>( + 'indices.existsAlias', + { name } + ); } catch (err) { throw new Error(`error checking existance of initial index: ${err.message}`); } return result as boolean; } - public async createIndex(name: string, body: any = {}): Promise { + public async createIndex(name: string, body: unknown = {}): Promise { try { - await this.callEs('indices.create', { + await this.callEs>('indices.create', { index: name, body, }); @@ -125,12 +135,12 @@ export class ClusterClientAdapter { ): Promise { try { const { - hits: { - hits, - total: { value: total }, - }, - } = await this.callEs('search', { + hits: { hits, total }, + }: SearchResponse = await this.callEs('search', { index, + // The SearchResponse type only supports total as an int, + // so we're forced to explicitly request that it return as an int + rest_total_hits_as_int: true, body: { size: perPage, from: (page - 1) * perPage, @@ -189,7 +199,7 @@ export class ClusterClientAdapter { page, per_page: perPage, total, - data: hits.map((hit: any) => hit._source) as IEvent[], + data: hits.map(hit => hit._source) as IEvent[], }; } catch (err) { throw new Error( @@ -198,13 +208,15 @@ export class ClusterClientAdapter { } } - private async callEs(operation: string, body?: any): Promise { + // We have a common problem typing ES-DSL Queries + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private async callEs(operation: string, body?: any) { try { this.debug(`callEs(${operation}) calls:`, body); const clusterClient = await this.clusterClientPromise; const result = await clusterClient.callAsInternalUser(operation, body); this.debug(`callEs(${operation}) result:`, result); - return result; + return result as ESQueryResult; } catch (err) { this.debug(`callEs(${operation}) error:`, { message: err.message, @@ -214,7 +226,7 @@ export class ClusterClientAdapter { } } - private debug(message: string, object?: any) { + private debug(message: string, object?: unknown) { const objectString = object == null ? '' : JSON.stringify(object); this.logger.debug(`esContext: ${message} ${objectString}`); } diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts index 982454e671008..a6af209d6d3a0 100644 --- a/x-pack/plugins/event_log/server/es/documents.ts +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -9,7 +9,7 @@ import mappings from '../../generated/mappings.json'; // returns the body of an index template used in an ES indices.putTemplate call export function getIndexTemplate(esNames: EsNames) { - const indexTemplateBody: any = { + const indexTemplateBody = { index_patterns: [esNames.indexPatternWithVersion], settings: { number_of_shards: 1, diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts index 6db16ebadd4ce..58dd3ae6eb514 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -40,7 +40,7 @@ describe('EventLogClientService', () => { function fakeRequest(): KibanaRequest { const savedObjectsClient = savedObjectsClientMock.create(); - return { + return ({ headers: {}, getBasePath: () => '', path: '/', @@ -54,5 +54,5 @@ function fakeRequest(): KibanaRequest { }, }, getSavedObjectsClient: () => savedObjectsClient, - } as any; + } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_logger.ts b/x-pack/plugins/event_log/server/event_logger.ts index f5149da069953..bcfd7bd45a6f5 100644 --- a/x-pack/plugins/event_log/server/event_logger.ts +++ b/x-pack/plugins/event_log/server/event_logger.ts @@ -168,7 +168,7 @@ function indexEventDoc(esContext: EsContext, doc: Doc): void { } // whew, the thing that actually writes the event log document! -async function indexLogEventDoc(esContext: EsContext, doc: any) { +async function indexLogEventDoc(esContext: EsContext, doc: unknown) { esContext.logger.debug(`writing to event log: ${JSON.stringify(doc)}`); await esContext.waitTillReady(); await esContext.esAdapter.indexDocument(doc); @@ -176,6 +176,6 @@ async function indexLogEventDoc(esContext: EsContext, doc: any) { } // TODO: write log entry to a bounded queue buffer -function writeLogEventDocOnError(esContext: EsContext, doc: any) { +function writeLogEventDocOnError(esContext: EsContext, doc: unknown) { esContext.logger.warn(`unable to write event doc: ${JSON.stringify(doc)}`); } diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts index d4dbb9064a1ba..6f1d92034c06f 100644 --- a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts +++ b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts @@ -16,11 +16,11 @@ describe('ReadySignal', () => { test('works as expected', async done => { let value = 41; - timeoutSet(100, () => { + timeoutSet(100, async () => { expect(value).toBe(41); }); - timeoutSet(250, () => readySignal.signal(42)); + timeoutSet(250, async () => readySignal.signal(42)); timeoutSet(400, async () => { expect(value).toBe(42); @@ -35,6 +35,6 @@ describe('ReadySignal', () => { }); }); -function timeoutSet(ms: number, fn: any) { +function timeoutSet(ms: number, fn: () => Promise): void { setTimeout(fn, ms); } diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index e5034f599f118..dd83b2cfb03b8 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -120,7 +120,7 @@ export class Plugin implements CorePlugin, + RequestHandler, 'eventLog' > => { return async (context, request) => { diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts index 19933649277aa..2d5e37e870b28 100644 --- a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -11,9 +11,9 @@ import { IEventLogClient } from '../types'; export function mockHandlerArguments( eventLogClient: IEventLogClient, - req: any, + req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { return [ ({ eventLog: { @@ -22,7 +22,7 @@ export function mockHandlerArguments( }, }, } as unknown) as RequestHandlerContext, - req as KibanaRequest, + req as KibanaRequest, mockResponseFactory(res), ]; } diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts index cb170e50fb447..f8e1c842ae436 100644 --- a/x-pack/plugins/event_log/server/routes/find.ts +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -31,9 +31,9 @@ export const findRoute = (router: IRouter) => { }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, FindOptionsType, any, any>, + req: KibanaRequest, FindOptionsType, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { if (!context.eventLog) { return res.badRequest({ body: 'RouteHandlerContext is not registered for eventLog' }); } diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index f7962f7011f34..8e877f696a2fc 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -7,7 +7,7 @@ import { configSchema } from './config'; describe('config validation', () => { test('task manager defaults', () => { - const config: Record = {}; + const config: Record = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { "enabled": true, @@ -21,7 +21,7 @@ describe('config validation', () => { }); test('the ElastiSearch Tasks index cannot be used for task manager', () => { - const config: Record = { + const config: Record = { index: '.tasks', }; expect(() => { diff --git a/x-pack/plugins/task_manager/server/create_task_manager.ts b/x-pack/plugins/task_manager/server/create_task_manager.ts index 9ff97bbcc17e6..7ab6acba7976d 100644 --- a/x-pack/plugins/task_manager/server/create_task_manager.ts +++ b/x-pack/plugins/task_manager/server/create_task_manager.ts @@ -12,9 +12,10 @@ import { } from '../../../../src/core/server'; import { TaskManager } from './task_manager'; import { Logger } from './types'; +import { TaskManagerConfig } from './config'; export interface LegacyDeps { - config: any; + config: unknown; elasticsearch: Pick; savedObjectsRepository: ISavedObjectsRepository; savedObjectsSerializer: SavedObjectsSerializer; @@ -33,7 +34,7 @@ export function createTaskManager( ) { return new TaskManager({ taskManagerId: core.uuid.getInstanceUuid(), - config, + config: config as TaskManagerConfig, savedObjectsRepository, serializer: savedObjectsSerializer, callAsInternalUser, diff --git a/x-pack/plugins/task_manager/server/lib/middleware.test.ts b/x-pack/plugins/task_manager/server/lib/middleware.test.ts index 3aa39eb3db513..abf69e726262f 100644 --- a/x-pack/plugins/task_manager/server/lib/middleware.test.ts +++ b/x-pack/plugins/task_manager/server/lib/middleware.test.ts @@ -28,9 +28,9 @@ const getMockConcreteTaskInstance = () => { scheduledAt: Date; startedAt: Date | null; retryAt: Date | null; - state: any; + state: unknown; taskType: string; - params: any; + params: unknown; ownerId: string | null; } = { id: 'hy8o99o83', @@ -47,7 +47,7 @@ const getMockConcreteTaskInstance = () => { params: { abc: 'def' }, ownerId: null, }; - return concrete; + return (concrete as unknown) as ConcreteTaskInstance; }; const getMockRunContext = (runTask: ConcreteTaskInstance) => ({ taskInstance: runTask, @@ -95,7 +95,7 @@ describe('addMiddlewareToChain', () => { await middlewareChain .beforeSave({ taskInstance: getMockTaskInstance() }) - .then((saveOpts: any) => { + .then((saveOpts: unknown) => { expect(saveOpts).toMatchInlineSnapshot(` Object { "taskInstance": Object { diff --git a/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts b/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts index da64befe28673..650eb36347c86 100644 --- a/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts +++ b/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { RunContext } from '../task'; +import { RunContext, TaskDictionary, TaskDefinition } from '../task'; import { sanitizeTaskDefinitions } from './sanitize_task_definitions'; interface Opts { @@ -14,7 +14,7 @@ interface Opts { const getMockTaskDefinitions = (opts: Opts) => { const { numTasks } = opts; - const tasks: any = {}; + const tasks: Record = {}; for (let i = 0; i < numTasks; i++) { const type = `test_task_type_${i}`; @@ -35,7 +35,7 @@ const getMockTaskDefinitions = (opts: Opts) => { }, }; } - return tasks; + return (tasks as unknown) as TaskDictionary; }; describe('sanitizeTaskDefinitions', () => { diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index e837fcd9c0dec..a70fbdb18c30b 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -35,7 +35,7 @@ export class TaskManagerPlugin this.currentConfig = {} as TaskManagerConfig; } - public setup(core: CoreSetup, plugins: any): TaskManagerSetupContract { + public setup(core: CoreSetup, plugins: unknown): TaskManagerSetupContract { const logger = this.initContext.logger.get('taskManager'); const config$ = this.initContext.config.create(); return { diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts index 8f7cc47f936b2..4fd4da3d83a36 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts @@ -55,6 +55,7 @@ export const IdleTaskWithExpiredRunAt: MustCondition = }; // TODO: Fix query clauses to support this +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const InactiveTasks: BoolClauseWithAnyCondition = { bool: { must_not: [ diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index 48e87582ce3fe..e806a866ff0b7 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -28,7 +28,7 @@ type Require = Omit & Required Promise; +export type ElasticJs = (action: string, args: unknown) => Promise; /** * The run context is passed into a task's run function as its sole argument. @@ -61,12 +61,12 @@ export interface RunResult { * The state which will be passed to the next run of this task (if this is a * recurring task). See the RunContext type definition for more details. */ - state: Record; + state: Record; } export interface SuccessfulRunResult { runAt?: Date; - state?: Record; + state?: Record; } export interface FailedRunResult extends SuccessfulRunResult { @@ -237,6 +237,9 @@ export interface TaskInstance { * A task-specific set of parameters, used by the task's run function to tailor * its work. This is generally user-input, such as { sms: '333-444-2222' }. */ + // we allow any here as unknown will break current use in other plugins + // this can be fixed by supporting generics in the future + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; /** @@ -244,6 +247,9 @@ export interface TaskInstance { * run. If there was no previous run, or if the previous run did not return * any state, this will be the empy object: {} */ + // we allow any here as unknown will break current use in other plugins + // this can be fixed by supporting generics in the future + // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record; /** @@ -336,6 +342,9 @@ export interface ConcreteTaskInstance extends TaskInstance { * run. If there was no previous run, or if the previous run did not return * any state, this will be the empy object: {} */ + // we allow any here as unknown will break current use in other plugins + // this can be fixed by supporting generics in the future + // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record; /** @@ -343,3 +352,15 @@ export interface ConcreteTaskInstance extends TaskInstance { */ ownerId: string | null; } + +export type SerializedConcreteTaskInstance = Omit< + ConcreteTaskInstance, + 'state' | 'params' | 'scheduledAt' | 'startedAt' | 'retryAt' | 'runAt' +> & { + state: string; + params: string; + scheduledAt: string; + startedAt: string | null; + retryAt: string | null; + runAt: string; +}; diff --git a/x-pack/plugins/task_manager/server/task_events.ts b/x-pack/plugins/task_manager/server/task_events.ts index 063ac2499471f..b17a3636c1730 100644 --- a/x-pack/plugins/task_manager/server/task_events.ts +++ b/x-pack/plugins/task_manager/server/task_events.ts @@ -68,16 +68,18 @@ export function asTaskRunRequestEvent( } export function isTaskMarkRunningEvent( - taskEvent: TaskEvent + taskEvent: TaskEvent ): taskEvent is TaskMarkRunning { return taskEvent.type === TaskEventType.TASK_MARK_RUNNING; } -export function isTaskRunEvent(taskEvent: TaskEvent): taskEvent is TaskRun { +export function isTaskRunEvent(taskEvent: TaskEvent): taskEvent is TaskRun { return taskEvent.type === TaskEventType.TASK_RUN; } -export function isTaskClaimEvent(taskEvent: TaskEvent): taskEvent is TaskClaim { +export function isTaskClaimEvent(taskEvent: TaskEvent): taskEvent is TaskClaim { return taskEvent.type === TaskEventType.TASK_CLAIM; } -export function isTaskRunRequestEvent(taskEvent: TaskEvent): taskEvent is TaskRunRequest { +export function isTaskRunRequestEvent( + taskEvent: TaskEvent +): taskEvent is TaskRunRequest { return taskEvent.type === TaskEventType.TASK_RUN_REQUEST; } diff --git a/x-pack/plugins/task_manager/server/task_manager.test.ts b/x-pack/plugins/task_manager/server/task_manager.test.ts index 3d48ce18c9d6a..3f3b14a791f24 100644 --- a/x-pack/plugins/task_manager/server/task_manager.test.ts +++ b/x-pack/plugins/task_manager/server/task_manager.test.ts @@ -25,6 +25,7 @@ import { SavedObjectsSerializer, SavedObjectTypeRegistry } from '../../../../src import { mockLogger } from './test_utils'; import { asErr, asOk } from './lib/result_type'; import { ConcreteTaskInstance, TaskLifecycleResult, TaskStatus } from './task'; +import { Middleware } from './lib/middleware'; const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = new SavedObjectsSerializer(new SavedObjectTypeRegistry()); @@ -247,20 +248,20 @@ describe('TaskManager', () => { test('allows middleware registration before starting', () => { const client = new TaskManager(taskManagerOpts); - const middleware = { - beforeSave: async (saveOpts: any) => saveOpts, - beforeRun: async (runOpts: any) => runOpts, - beforeMarkRunning: async (runOpts: any) => runOpts, + const middleware: Middleware = { + beforeSave: jest.fn(async saveOpts => saveOpts), + beforeRun: jest.fn(async runOpts => runOpts), + beforeMarkRunning: jest.fn(async runOpts => runOpts), }; expect(() => client.addMiddleware(middleware)).not.toThrow(); }); test('disallows middleware registration after starting', async () => { const client = new TaskManager(taskManagerOpts); - const middleware = { - beforeSave: async (saveOpts: any) => saveOpts, - beforeRun: async (runOpts: any) => runOpts, - beforeMarkRunning: async (runOpts: any) => runOpts, + const middleware: Middleware = { + beforeSave: jest.fn(async saveOpts => saveOpts), + beforeRun: jest.fn(async runOpts => runOpts), + beforeMarkRunning: jest.fn(async runOpts => runOpts), }; client.start(); diff --git a/x-pack/plugins/task_manager/server/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts index a7c67d190e72e..24ceea0fe71ef 100644 --- a/x-pack/plugins/task_manager/server/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -43,6 +43,7 @@ import { TaskLifecycle, TaskLifecycleResult, TaskStatus, + ElasticJs, } from './task'; import { createTaskPoller, PollingError, PollingErrorType } from './task_poller'; import { TaskPool } from './task_pool'; @@ -129,7 +130,7 @@ export class TaskManager { this.store = new TaskStore({ serializer: opts.serializer, savedObjectsRepository: opts.savedObjectsRepository, - callCluster: opts.callAsInternalUser, + callCluster: (opts.callAsInternalUser as unknown) as ElasticJs, index: opts.config.index, maxAttempts: opts.config.max_attempts, definitions: this.definitions, @@ -273,7 +274,7 @@ export class TaskManager { */ public async schedule( taskInstance: TaskInstanceWithDeprecatedFields, - options?: any + options?: object ): Promise { await this.waitUntilStarted(); const { taskInstance: modifiedTask } = await this.middleware.beforeSave({ @@ -308,7 +309,7 @@ export class TaskManager { */ public async ensureScheduled( taskInstance: TaskInstanceWithId, - options?: any + options?: object ): Promise { try { return await this.schedule(taskInstance, options); diff --git a/x-pack/plugins/task_manager/server/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_runner.test.ts index fad3bf96905ae..07247dcb1da47 100644 --- a/x-pack/plugins/task_manager/server/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_runner.test.ts @@ -9,7 +9,7 @@ import sinon from 'sinon'; import { minutesFromNow } from './lib/intervals'; import { asOk, asErr } from './lib/result_type'; import { TaskEvent, asTaskRunEvent, asTaskMarkRunningEvent } from './task_events'; -import { ConcreteTaskInstance, TaskStatus } from './task'; +import { ConcreteTaskInstance, TaskStatus, TaskDictionary, TaskDefinition } from './task'; import { TaskManagerRunner } from './task_runner'; import { mockLogger } from './test_utils'; import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; @@ -906,8 +906,8 @@ describe('TaskManagerRunner', () => { interface TestOpts { instance?: Partial; - definitions?: any; - onTaskEvent?: (event: TaskEvent) => void; + definitions?: unknown; + onTaskEvent?: (event: TaskEvent) => void; } function testOpts(opts: TestOpts) { @@ -956,7 +956,7 @@ describe('TaskManagerRunner', () => { title: 'Bar!', createTaskRunner, }, - }), + }) as TaskDictionary, onTaskEvent: opts.onTaskEvent, }); @@ -970,7 +970,7 @@ describe('TaskManagerRunner', () => { }; } - async function testReturn(result: any, shouldBeValid: boolean) { + async function testReturn(result: unknown, shouldBeValid: boolean) { const { runner, logger } = testOpts({ definitions: { bar: { @@ -991,11 +991,11 @@ describe('TaskManagerRunner', () => { } } - function allowsReturnType(result: any) { + function allowsReturnType(result: unknown) { return testReturn(result, true); } - function disallowsReturnType(result: any) { + function disallowsReturnType(result: unknown) { return testReturn(result, false); } }); diff --git a/x-pack/plugins/task_manager/server/task_runner.ts b/x-pack/plugins/task_manager/server/task_runner.ts index ec1c40dc80731..7a9fa0c45e15f 100644 --- a/x-pack/plugins/task_manager/server/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_runner.ts @@ -409,7 +409,7 @@ export class TaskManagerRunner implements TaskRunner { attempts, addDuration, }: { - error: any; + error: Error; attempts: number; addDuration?: string; }): Date | null { diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 8f122230d5965..6524ea212e7c5 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -15,12 +15,15 @@ import { TaskInstance, TaskStatus, TaskLifecycleResult, + SerializedConcreteTaskInstance, + ConcreteTaskInstance, } from './task'; import { StoreOpts, OwnershipClaimingOpts, TaskStore, SearchOpts } from './task_store'; import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; import { SavedObjectsSerializer, SavedObjectTypeRegistry, + SavedObjectAttributes, SavedObjectsErrorHelpers, } from 'src/core/server'; import { asTaskClaimEvent, TaskEvent } from './task_events'; @@ -50,6 +53,7 @@ const serializer = new SavedObjectsSerializer(new SavedObjectTypeRegistry()); beforeEach(() => jest.resetAllMocks()); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any (global as any).Date = class Date { constructor() { return mockedDate; @@ -61,9 +65,9 @@ const mockedDate = new Date('2019-02-12T21:01:22.479Z'); describe('TaskStore', () => { describe('schedule', () => { - async function testSchedule(task: TaskInstance) { + async function testSchedule(task: unknown) { const callCluster = jest.fn(); - savedObjectsClient.create.mockImplementation(async (type: string, attributes: any) => ({ + savedObjectsClient.create.mockImplementation(async (type: string, attributes: unknown) => ({ id: 'testid', type, attributes, @@ -79,7 +83,7 @@ describe('TaskStore', () => { definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, }); - const result = await store.schedule(task); + const result = await store.schedule(task as TaskInstance); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); @@ -152,14 +156,16 @@ describe('TaskStore', () => { test('sets runAt to now if not specified', async () => { await testSchedule({ taskType: 'dernstraight', params: {}, state: {} }); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); - const attributes: any = savedObjectsClient.create.mock.calls[0][1]; + const attributes = savedObjectsClient.create.mock + .calls[0][1] as SerializedConcreteTaskInstance; expect(new Date(attributes.runAt as string).getTime()).toEqual(mockedDate.getTime()); }); test('ensures params and state are not null', async () => { - await testSchedule({ taskType: 'yawn' } as any); + await testSchedule({ taskType: 'yawn' }); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); - const attributes: any = savedObjectsClient.create.mock.calls[0][1]; + const attributes = savedObjectsClient.create.mock + .calls[0][1] as SerializedConcreteTaskInstance; expect(attributes.params).toEqual('{}'); expect(attributes.state).toEqual('{}'); }); @@ -172,8 +178,8 @@ describe('TaskStore', () => { }); describe('fetch', () => { - async function testFetch(opts?: SearchOpts, hits: any[] = []) { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits } })); + async function testFetch(opts?: SearchOpts, hits: unknown[] = []) { + const callCluster = sinon.spy(async (name: string, params?: unknown) => ({ hits: { hits } })); const store = new TaskStore({ index: 'tasky', taskManagerId: '', @@ -232,11 +238,11 @@ describe('TaskStore', () => { claimingOpts, }: { opts: Partial; - hits?: any[]; + hits?: unknown[]; claimingOpts: OwnershipClaimingOpts; }) { const versionConflicts = 2; - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: hits.length + versionConflicts, @@ -269,7 +275,7 @@ describe('TaskStore', () => { } test('it returns normally with no tasks when the index does not exist.', async () => { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ + const callCluster = sinon.spy(async (name: string, params?: unknown) => ({ total: 0, updated: 0, })); @@ -748,7 +754,7 @@ if (doc['task.runAt'].size()!=0) { }; savedObjectsClient.update.mockImplementation( - async (type: string, id: string, attributes: any) => { + async (type: string, id: string, attributes: SavedObjectAttributes) => { return { id, type, @@ -1020,7 +1026,7 @@ if (doc['task.runAt'].size()!=0) { test('emits an event when a task is succesfully claimed by id', async done => { const { taskManagerId, runAt, tasks } = generateTasks(); - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: tasks.length, @@ -1039,9 +1045,9 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'aaa')) + .pipe(filter((event: TaskEvent) => event.id === 'aaa')) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent) => { expect(event).toMatchObject( asTaskClaimEvent( 'aaa', @@ -1077,7 +1083,7 @@ if (doc['task.runAt'].size()!=0) { test('emits an event when a task is succesfully by scheduling', async done => { const { taskManagerId, runAt, tasks } = generateTasks(); - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: tasks.length, @@ -1096,9 +1102,9 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'bbb')) + .pipe(filter((event: TaskEvent) => event.id === 'bbb')) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent) => { expect(event).toMatchObject( asTaskClaimEvent( 'bbb', @@ -1134,7 +1140,7 @@ if (doc['task.runAt'].size()!=0) { test('emits an event when the store fails to claim a required task by id', async done => { const { taskManagerId, tasks } = generateTasks(); - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: tasks.length, @@ -1153,9 +1159,9 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'ccc')) + .pipe(filter((event: TaskEvent) => event.id === 'ccc')) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent) => { expect(event).toMatchObject( asTaskClaimEvent('ccc', asErr(new Error(`failed to claim task 'ccc'`))) ); diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 0e487386eb04d..01299615c7d49 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -9,11 +9,11 @@ */ import apm from 'elastic-apm-node'; import { Subject, Observable } from 'rxjs'; -import { omit, difference } from 'lodash'; +import { omit, difference, defaults } from 'lodash'; +import { SearchResponse, UpdateDocumentByQueryResponse } from 'elasticsearch'; import { SavedObject, - SavedObjectAttributes, SavedObjectsSerializer, SavedObjectsRawDoc, ISavedObjectsRepository, @@ -29,6 +29,7 @@ import { TaskInstance, TaskLifecycle, TaskLifecycleResult, + SerializedConcreteTaskInstance, } from './task'; import { TaskClaim, asTaskClaimEvent } from './task_events'; @@ -71,7 +72,7 @@ export interface SearchOpts { query?: object; size?: number; seq_no_primary_term?: boolean; - search_after?: any[]; + search_after?: unknown[]; } export interface UpdateByQuerySearchOpts extends SearchOpts { @@ -166,7 +167,7 @@ export class TaskStore { ); } - const savedObject = await this.savedObjectsRepository.create( + const savedObject = await this.savedObjectsRepository.create( 'task', taskInstanceToAttributes(taskInstance), { id: taskInstance.id, refresh: false } @@ -314,17 +315,21 @@ export class TaskStore { * @returns {Promise} */ public async update(doc: ConcreteTaskInstance): Promise { - const updatedSavedObject = await this.savedObjectsRepository.update( - 'task', - doc.id, - taskInstanceToAttributes(doc), - { - refresh: false, - version: doc.version, - } - ); + const attributes = taskInstanceToAttributes(doc); + const updatedSavedObject = await this.savedObjectsRepository.update< + SerializedConcreteTaskInstance + >('task', doc.id, attributes, { + refresh: false, + version: doc.version, + }); - return savedObjectToConcreteTaskInstance(updatedSavedObject); + return savedObjectToConcreteTaskInstance( + // The SavedObjects update api forces a Partial on the `attributes` on the response, + // but actually returns the whole object that is passed to it, so as we know we're + // passing in the whole object, this is safe to do. + // This is far from ideal, but unless we change the SavedObjectsClient this is the best we can do + { ...updatedSavedObject, attributes: defaults(updatedSavedObject.attributes, attributes) } + ); } /** @@ -377,12 +382,12 @@ export class TaskStore { }, }); - const rawDocs = result.hits.hits; + const rawDocs = (result as SearchResponse).hits.hits; return { docs: (rawDocs as SavedObjectsRawDoc[]) .map(doc => this.serializer.rawToSavedObject(doc)) - .map(doc => omit(doc, 'namespace') as SavedObject) + .map(doc => omit(doc, 'namespace') as SavedObject) .map(savedObjectToConcreteTaskInstance), }; } @@ -404,7 +409,7 @@ export class TaskStore { }, }); - const { total, updated, version_conflicts } = result; + const { total, updated, version_conflicts } = result as UpdateDocumentByQueryResponse; return { total, updated, @@ -413,7 +418,7 @@ export class TaskStore { } } -function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes { +function taskInstanceToAttributes(doc: TaskInstance): SerializedConcreteTaskInstance { return { ...omit(doc, 'id', 'version'), params: JSON.stringify(doc.params || {}), @@ -428,8 +433,7 @@ function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes { } export function savedObjectToConcreteTaskInstance( - // TODO: define saved object type - savedObject: Omit, 'references'> + savedObject: Omit, 'references'> ): ConcreteTaskInstance { return { ...savedObject.attributes, @@ -437,8 +441,8 @@ export function savedObjectToConcreteTaskInstance( version: savedObject.version, scheduledAt: new Date(savedObject.attributes.scheduledAt), runAt: new Date(savedObject.attributes.runAt), - startedAt: savedObject.attributes.startedAt && new Date(savedObject.attributes.startedAt), - retryAt: savedObject.attributes.retryAt && new Date(savedObject.attributes.retryAt), + startedAt: savedObject.attributes.startedAt ? new Date(savedObject.attributes.startedAt) : null, + retryAt: savedObject.attributes.retryAt ? new Date(savedObject.attributes.retryAt) : null, state: parseJSONField(savedObject.attributes.state, 'state', savedObject.id), params: parseJSONField(savedObject.attributes.params, 'params', savedObject.id), }; diff --git a/x-pack/plugins/task_manager/server/test_utils/index.ts b/x-pack/plugins/task_manager/server/test_utils/index.ts index 719ccadbe33dd..3dfc53672b46f 100644 --- a/x-pack/plugins/task_manager/server/test_utils/index.ts +++ b/x-pack/plugins/task_manager/server/test_utils/index.ts @@ -33,11 +33,11 @@ interface Resolvable { */ export function resolvable(): PromiseLike & Resolvable { let resolve: () => void; - const result = new Promise(r => (resolve = r)) as any; - - result.resolve = () => nativeTimeout(resolve, 0); - - return result; + return Object.assign(new Promise(r => (resolve = r)), { + resolve() { + return nativeTimeout(resolve, 0); + }, + }); } /** From e918c43535d4109b35ced0723c931ff3e71362bf Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 24 Apr 2020 12:53:28 -0400 Subject: [PATCH 12/13] [Uptime] Update TLS settings (#64111) * Refactor settings form event handling and modify certs fields. * Fix/improve broken types/unit/integration/api tests. * Modify default expiration threshold. * Rename test vars. * Implement PR feedback. * Refresh snapshots, fix broken tests/types. * Remove unnecessary state spreading. * Add type for settings field errors. * Refresh test snapshots. * Improve punctuation. --- .../plugins/uptime/common/constants/index.ts | 1 + .../common/constants/settings_defaults.ts | 15 ++ .../common/runtime_types/dynamic_settings.ts | 28 +- .../certificate_form.test.tsx.snap | 9 +- .../__snapshots__/indices_form.test.tsx.snap | 9 +- .../__tests__/certificate_form.test.tsx | 5 +- .../settings/__tests__/indices_form.test.tsx | 5 +- .../components/settings/certificate_form.tsx | 248 +++++++++--------- .../components/settings/indices_form.tsx | 115 ++++---- .../plugins/uptime/public/pages/settings.tsx | 87 +++--- .../uptime/public/pages/translations.ts | 23 ++ .../public/state/reducers/dynamic_settings.ts | 22 +- .../state/selectors/__tests__/index.test.ts | 2 + .../uptime/public/state/selectors/index.ts | 4 +- .../lib/alerts/__tests__/status_check.test.ts | 16 +- .../lib/requests/__tests__/get_certs.test.ts | 6 +- .../__tests__/get_latest_monitor.test.ts | 6 +- .../__tests__/get_monitor_charts.test.ts | 6 +- .../__tests__/get_monitor_status.test.ts | 10 +- .../__tests__/get_ping_histogram.test.ts | 14 +- .../lib/requests/__tests__/get_pings.test.ts | 16 +- .../uptime/server/lib/saved_objects.ts | 14 +- .../apis/uptime/rest/dynamic_settings.ts | 14 +- .../test/functional/apps/uptime/settings.ts | 20 +- .../functional/services/uptime/settings.ts | 21 +- 25 files changed, 381 insertions(+), 335 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts create mode 100644 x-pack/legacy/plugins/uptime/public/pages/translations.ts diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts index 74783cf46550f..72d498056d6b3 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/index.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts @@ -9,6 +9,7 @@ export { CHART_FORMAT_LIMITS } from './chart_format_limits'; export { CLIENT_DEFAULTS } from './client_defaults'; export { CONTEXT_DEFAULTS } from './context_defaults'; export * from './capabilities'; +export * from './settings_defaults'; export { PLUGIN } from './plugin'; export { QUERY, STATES } from './query'; export * from './ui'; diff --git a/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts new file mode 100644 index 0000000000000..b7986679a09ca --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DynamicSettings } from '../runtime_types'; + +export const DYNAMIC_SETTINGS_DEFAULTS: DynamicSettings = { + heartbeatIndices: 'heartbeat-8*', + certThresholds: { + expiration: 30, + age: 365, + }, +}; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts index 985b51891da99..da887cc5055c1 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts @@ -6,19 +6,15 @@ import * as t from 'io-ts'; -export const CertificatesStatesThresholdType = t.interface({ - warningState: t.number, - errorState: t.number, +export const CertStateThresholdsType = t.type({ + age: t.number, + expiration: t.number, }); -export const DynamicSettingsType = t.intersection([ - t.type({ - heartbeatIndices: t.string, - }), - t.partial({ - certificatesThresholds: CertificatesStatesThresholdType, - }), -]); +export const DynamicSettingsType = t.type({ + heartbeatIndices: t.string, + certThresholds: CertStateThresholdsType, +}); export const DynamicSettingsSaveType = t.intersection([ t.type({ @@ -31,12 +27,4 @@ export const DynamicSettingsSaveType = t.intersection([ export type DynamicSettings = t.TypeOf; export type DynamicSettingsSaveResponse = t.TypeOf; -export type CertificatesStatesThreshold = t.TypeOf; - -export const defaultDynamicSettings: DynamicSettings = { - heartbeatIndices: 'heartbeat-8*', - certificatesThresholds: { - errorState: 7, - warningState: 30, - }, -}; +export type CertStateThresholds = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap index 36bc9bb860211..96d472c91680d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap @@ -52,17 +52,18 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = } > diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap index 93151198c0f49..3b0c6d99fd9f8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap @@ -52,17 +52,18 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = } > diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index a3158f3d72445..3d4bd58aabe0f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -13,12 +13,13 @@ describe('CertificateForm', () => { expect( shallowWithRouter( ) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx index 654d51019d4e5..07a3bf81e39d8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx @@ -13,12 +13,13 @@ describe('CertificateForm', () => { expect( shallowWithRouter( ) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx index 5103caee1e1c0..209e38785e165 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx @@ -6,155 +6,157 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useSelector } from 'react-redux'; import { EuiDescribedFormGroup, EuiFormRow, EuiCode, EuiFieldNumber, + EuiText, EuiTitle, EuiSpacer, - EuiSelect, EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { defaultDynamicSettings, DynamicSettings } from '../../../common/runtime_types'; -import { selectDynamicSettings } from '../../state/selectors'; +import { CertStateThresholds } from '../../../common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; +import { SettingsFormProps } from '../../pages/settings'; -type NumStr = string | number; - -export type OnFieldChangeType = (field: string, value?: NumStr) => void; - -export interface SettingsFormProps { - onChange: OnFieldChangeType; - formFields: DynamicSettings | null; - fieldErrors: any; - isDisabled: boolean; +interface ChangedValues { + heartbeatIndices?: string; + certThresholds?: Partial; } +export type OnFieldChangeType = (changedValues: ChangedValues) => void; + export const CertificateExpirationForm: React.FC = ({ + loading, onChange, formFields, fieldErrors, isDisabled, -}) => { - const dss = useSelector(selectDynamicSettings); - - return ( - <> - -

+}) => ( + <> + +

+ +

+
+ + +

+ } + description={ + + } + > + {DYNAMIC_SETTINGS_DEFAULTS.certThresholds.expiration} + ), + }} /> - - - - - - } - description={ + isInvalid={!!fieldErrors?.certificatesThresholds?.expirationThresholdError} + label={ } > - {defaultDynamicSettings?.certificatesThresholds?.errorState} - ), - }} - /> - } - isInvalid={!!fieldErrors?.certificatesThresholds?.errorState} - label={ - + + + onChange({ + certThresholds: { + expiration: Number(e.target.value), + }, + }) + } /> - } - > - - - - onChange( - 'certificatesThresholds.errorState', - value === '' ? undefined : Number(value) - ) - } + + + + - - - - - - - {defaultDynamicSettings?.certificatesThresholds?.warningState} - ), - }} - /> - } - isInvalid={!!fieldErrors?.certificatesThresholds?.warningState} - label={ - + + + + {DYNAMIC_SETTINGS_DEFAULTS.certThresholds.age}, + }} + /> + } + isInvalid={!!fieldErrors?.certificatesThresholds?.ageThresholdError} + label={ + + } + > + + + + onChange({ + certThresholds: { age: Number(e.currentTarget.value) }, + }) + } /> - } - > - - - - onChange('certificatesThresholds.warningState', Number(event.currentTarget.value)) - } + + + + - - - - - - - - - ); -}; + + + + + + +); diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx index c28eca2ea229e..b9a5ca0e730de 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useSelector } from 'react-redux'; import { EuiDescribedFormGroup, EuiFormRow, @@ -15,76 +14,72 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { defaultDynamicSettings } from '../../../common/runtime_types'; -import { selectDynamicSettings } from '../../state/selectors'; -import { SettingsFormProps } from './certificate_form'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; +import { SettingsFormProps } from '../../pages/settings'; export const IndicesForm: React.FC = ({ onChange, + loading, formFields, fieldErrors, isDisabled, -}) => { - const dss = useSelector(selectDynamicSettings); - - return ( - <> - -

+}) => ( + <> + +

+ +

+
+ + + +

+ } + description={ + + } + > + {DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices}, + }} /> - -
- - - - } - description={ + isInvalid={!!fieldErrors?.heartbeatIndices} + label={ } > - {defaultDynamicSettings.heartbeatIndices}, - }} - /> - } - isInvalid={!!fieldErrors?.heartbeatIndices} - label={ - - } - > - onChange('heartbeatIndices', event.currentTarget.value)} - /> - - - - ); -}; + disabled={isDisabled} + isLoading={loading} + value={formFields?.heartbeatIndices || ''} + onChange={(event: any) => onChange({ heartbeatIndices: event.currentTarget.value })} + /> + + + +); diff --git a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx index 6defb96e0da3d..d8c2a78092854 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx @@ -17,8 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch, useSelector } from 'react-redux'; -import { cloneDeep, isEqual, set } from 'lodash'; -import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; import { Link } from 'react-router-dom'; import { selectDynamicSettings } from '../state/selectors'; import { getDynamicSettings, setDynamicSettings } from '../state/actions/dynamic_settings'; @@ -32,21 +31,38 @@ import { CertificateExpirationForm, OnFieldChangeType, } from '../components/settings/certificate_form'; - -const getFieldErrors = (formFields: DynamicSettings | null) => { +import * as Translations from './translations'; + +interface SettingsPageFieldErrors { + heartbeatIndices: 'May not be blank' | ''; + certificatesThresholds: { + expirationThresholdError: string | null; + ageThresholdError: string | null; + } | null; +} + +export interface SettingsFormProps { + loading: boolean; + onChange: OnFieldChangeType; + formFields: DynamicSettings | null; + fieldErrors: SettingsPageFieldErrors | null; + isDisabled: boolean; +} + +const getFieldErrors = (formFields: DynamicSettings | null): SettingsPageFieldErrors | null => { if (formFields) { const blankStr = 'May not be blank'; - const { certificatesThresholds, heartbeatIndices } = formFields; + const { certThresholds: certificatesThresholds, heartbeatIndices } = formFields; const heartbeatIndErr = heartbeatIndices.match(/^\S+$/) ? '' : blankStr; - const errorStateErr = certificatesThresholds?.errorState ? null : blankStr; - const warningStateErr = certificatesThresholds?.warningState ? null : blankStr; + const expirationThresholdError = certificatesThresholds?.expiration ? null : blankStr; + const ageThresholdError = certificatesThresholds?.age ? null : blankStr; return { heartbeatIndices: heartbeatIndErr, certificatesThresholds: - errorStateErr || warningStateErr + expirationThresholdError || ageThresholdError ? { - errorState: errorStateErr, - warningState: warningStateErr, + expirationThresholdError, + ageThresholdError, } : null, }; @@ -57,10 +73,7 @@ const getFieldErrors = (formFields: DynamicSettings | null) => { export const SettingsPage = () => { const dss = useSelector(selectDynamicSettings); - const settingsBreadcrumbText = i18n.translate('xpack.uptime.settingsBreadcrumbText', { - defaultMessage: 'Settings', - }); - useBreadcrumbs([{ text: settingsBreadcrumbText }]); + useBreadcrumbs([{ text: Translations.settings.breadcrumbText }]); useUptimeTelemetry(UptimePage.Settings); @@ -70,21 +83,28 @@ export const SettingsPage = () => { dispatch(getDynamicSettings()); }, [dispatch]); - const [formFields, setFormFields] = useState(dss.settings || null); + const [formFields, setFormFields] = useState( + dss.settings ? { ...dss.settings } : null + ); - if (!dss.loadError && formFields == null && dss.settings) { - setFormFields({ ...dss.settings }); + if (!dss.loadError && formFields === null && dss.settings) { + setFormFields(Object.assign({}, { ...dss.settings })); } const fieldErrors = getFieldErrors(formFields); const isFormValid = !(fieldErrors && Object.values(fieldErrors).find(v => !!v)); - const onChangeFormField: OnFieldChangeType = (field, value) => { + const onChangeFormField: OnFieldChangeType = changedField => { if (formFields) { - const newFormFields = cloneDeep(formFields); - set(newFormFields, field, value); - setFormFields(cloneDeep(newFormFields)); + setFormFields({ + heartbeatIndices: changedField.heartbeatIndices ?? formFields.heartbeatIndices, + certThresholds: Object.assign( + {}, + formFields.certThresholds, + changedField?.certThresholds ?? null + ), + }); } }; @@ -95,27 +115,18 @@ export const SettingsPage = () => { } }; - const resetForm = () => { - if (formFields && dss.settings) { - setFormFields({ ...dss.settings }); - } - }; + const resetForm = () => setFormFields(dss.settings ? { ...dss.settings } : null); - const isFormDirty = dss.settings ? !isEqual(dss.settings, formFields) : true; + const isFormDirty = !isEqual(dss.settings, formFields); const canEdit: boolean = !!useKibana().services?.application?.capabilities.uptime.configureSettings || false; const isFormDisabled = dss.loading || !canEdit; - const editNoticeTitle = i18n.translate('xpack.uptime.settings.cannotEditTitle', { - defaultMessage: 'You do not have permission to edit settings.', - }); - const editNoticeText = i18n.translate('xpack.uptime.settings.cannotEditText', { - defaultMessage: - "Your user currently has 'Read' permissions for the Uptime app. Enable a permissions-level of 'All' to edit these settings.", - }); const cannotEditNotice = canEdit ? null : ( <> - {editNoticeText} + + {Translations.settings.editNoticeText} + ); @@ -124,9 +135,7 @@ export const SettingsPage = () => { <> - {i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', { - defaultMessage: 'Return to overview', - })} + {Translations.settings.returnToOverviewLinkLabel} @@ -139,12 +148,14 @@ export const SettingsPage = () => {
( ...state, loading: true, }), - [String(getDynamicSettingsSuccess)]: (state, action: Action) => { - return { - loading: false, - settings: action.payload, - }; - }, - [String(getDynamicSettingsFail)]: (state, action: Action) => { - return { - loading: false, - loadError: action.payload, - }; - }, + [String(getDynamicSettingsSuccess)]: (_state, action: Action) => ({ + loading: false, + settings: action.payload, + }), + [String(getDynamicSettingsFail)]: (_state, action: Action) => ({ + loading: false, + loadError: action.payload, + }), [String(setDynamicSettings)]: state => ({ ...state, loading: true, }), - [String(setDynamicSettingsSuccess)]: (state, action: Action) => ({ + [String(setDynamicSettingsSuccess)]: (_state, action: Action) => ({ settings: action.payload, saveSucceded: true, loading: false, diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts index 2b7c04178e9b4..ba5e5abf588b8 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -6,6 +6,7 @@ import { getBasePath, isIntegrationsPopupOpen } from '../index'; import { AppState } from '../../../state'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; describe('state selectors', () => { const state: AppState = { @@ -20,6 +21,7 @@ describe('state selectors', () => { loading: false, }, dynamicSettings: { + settings: DYNAMIC_SETTINGS_DEFAULTS, loading: false, }, monitor: { diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 7260c61f44147..15fc8b8a7b173 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -24,9 +24,7 @@ export const monitorLocationsSelector = (state: AppState, monitorId: string) => export const monitorStatusSelector = (state: AppState) => state.monitorStatus.status; -export const selectDynamicSettings = (state: AppState) => { - return state.dynamicSettings; -}; +export const selectDynamicSettings = (state: AppState) => state.dynamicSettings; export const selectIndexPattern = ({ indexPattern }: AppState) => { return { indexPattern: indexPattern.index_pattern, loading: indexPattern.loading }; diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 2cc6f23ebaae5..4f4c6e3011ad1 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -16,7 +16,7 @@ import { AlertType } from '../../../../../alerting/server'; import { IRouter } from 'kibana/server'; import { UMServerLibs } from '../../lib'; import { UptimeCoreSetup } from '../../adapters'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; /** @@ -52,7 +52,7 @@ const mockOptions = ( id: '', type: '', references: [], - attributes: defaultDynamicSettings, + attributes: DYNAMIC_SETTINGS_DEFAULTS, }); return { params, @@ -88,9 +88,9 @@ describe('status check alert', () => { Object { "callES": [MockFunction], "dynamicSettings": Object { - "certificatesThresholds": Object { - "errorState": 7, - "warningState": 30, + "certThresholds": Object { + "age": 365, + "expiration": 30, }, "heartbeatIndices": "heartbeat-8*", }, @@ -135,9 +135,9 @@ describe('status check alert', () => { Object { "callES": [MockFunction], "dynamicSettings": Object { - "certificatesThresholds": Object { - "errorState": 7, - "warningState": 30, + "certThresholds": Object { + "age": 365, + "expiration": 30, }, "heartbeatIndices": "heartbeat-8*", }, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts index 539344dfca791..b49a6b22ff976 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts @@ -5,6 +5,7 @@ */ import { getCerts } from '../get_certs'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; describe('getCerts', () => { let mockHits: any; @@ -86,7 +87,10 @@ describe('getCerts', () => { it('parses query result and returns expected values', async () => { const result = await getCerts({ callES: mockCallES, - dynamicSettings: { heartbeatIndices: 'heartbeat*' }, + dynamicSettings: { + heartbeatIndices: 'heartbeat*', + certThresholds: DYNAMIC_SETTINGS_DEFAULTS.certThresholds, + }, index: 1, from: 'now-2d', to: 'now+1h', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts index cf8414a3b0a68..03e2bc7a44bd0 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts @@ -5,14 +5,14 @@ */ import { getLatestMonitor } from '../get_latest_monitor'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; describe('getLatestMonitor', () => { let expectedGetLatestSearchParams: any; let mockEsSearchResult: any; beforeEach(() => { expectedGetLatestSearchParams = { - index: defaultDynamicSettings.heartbeatIndices, + index: DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices, body: { query: { bool: { @@ -64,7 +64,7 @@ describe('getLatestMonitor', () => { const mockEsClient = jest.fn(async (_request: any, _params: any) => mockEsSearchResult); const result = await getLatestMonitor({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateStart: 'now-1h', dateEnd: 'now', monitorId: 'testMonitor', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts index c740581734fdd..5d3f9ce8d4ad9 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts @@ -7,7 +7,7 @@ import { set } from 'lodash'; import mockChartsData from './monitor_charts_mock.json'; import { getMonitorDurationChart } from '../get_monitor_duration'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; describe('ElasticsearchMonitorsAdapter', () => { it('getMonitorChartsData will provide expected filters', async () => { @@ -16,7 +16,7 @@ describe('ElasticsearchMonitorsAdapter', () => { const search = searchMock.bind({}); await getMonitorDurationChart({ callES: search, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, monitorId: 'fooID', dateStart: 'now-15m', dateEnd: 'now', @@ -39,7 +39,7 @@ describe('ElasticsearchMonitorsAdapter', () => { expect( await getMonitorDurationChart({ callES: search, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, monitorId: 'id', dateStart: 'now-15m', dateEnd: 'now', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts index c94b5c96aa999..e47be617d7c99 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts @@ -6,8 +6,8 @@ import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; import { getMonitorStatus } from '../get_monitor_status'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; import { ScopedClusterClient } from 'src/core/server'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; interface BucketItemCriteria { monitor_id: string; @@ -103,7 +103,7 @@ describe('getMonitorStatus', () => { }`; await getMonitorStatus({ callES, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, filters: exampleFilter, locations: [], numTimes: 5, @@ -206,7 +206,7 @@ describe('getMonitorStatus', () => { const [callES, esMock] = setupMock([]); await getMonitorStatus({ callES, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, locations: ['fairbanks', 'harrisburg'], numTimes: 1, timerange: { @@ -329,7 +329,7 @@ describe('getMonitorStatus', () => { }; const result = await getMonitorStatus({ callES, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); @@ -494,7 +494,7 @@ describe('getMonitorStatus', () => { const [callES] = setupMock(criteria); const result = await getMonitorStatus({ callES, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, locations: [], numTimes: 5, timerange: { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts index faeb291bb533b..4de7d3ffd2a7d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts @@ -5,7 +5,7 @@ */ import { getPingHistogram } from '../get_ping_histogram'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; describe('getPingHistogram', () => { const standardMockResponse: any = { @@ -59,7 +59,7 @@ describe('getPingHistogram', () => { const result = await getPingHistogram({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, from: 'now-15m', to: 'now', filters: null, @@ -78,7 +78,7 @@ describe('getPingHistogram', () => { const result = await getPingHistogram({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, from: 'now-15m', to: 'now', filters: null, @@ -140,7 +140,7 @@ describe('getPingHistogram', () => { const result = await getPingHistogram({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, from: '1234', to: '5678', filters: JSON.stringify(searchFilter), @@ -196,7 +196,7 @@ describe('getPingHistogram', () => { const filters = `{"bool":{"must":[{"simple_query_string":{"query":"http"}}]}}`; const result = await getPingHistogram({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, from: 'now-15m', to: 'now', filters, @@ -213,7 +213,7 @@ describe('getPingHistogram', () => { mockEsClient.mockReturnValue(standardMockResponse); const result = await getPingHistogram({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, from: '1234', to: '5678', filters: '', @@ -234,7 +234,7 @@ describe('getPingHistogram', () => { const result = await getPingHistogram({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, from: '1234', to: '5678', filters: '', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts index fcf773db23de6..abd3655cc6402 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts @@ -6,7 +6,7 @@ import { getPings } from '../get_pings'; import { set } from 'lodash'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; describe('getAll', () => { let mockEsSearchResult: any; @@ -62,7 +62,7 @@ describe('getAll', () => { }, }; expectedGetAllParams = { - index: defaultDynamicSettings.heartbeatIndices, + index: DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices, body: { query: { bool: { @@ -88,7 +88,7 @@ describe('getAll', () => { mockEsClient.mockReturnValue(mockEsSearchResult); const result = await getPings({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateRange: { from: 'now-1h', to: 'now' }, sort: 'asc', size: 12, @@ -110,7 +110,7 @@ describe('getAll', () => { mockEsClient.mockReturnValue(mockEsSearchResult); await getPings({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateRange: { from: 'now-1h', to: 'now' }, sort: 'asc', size: 12, @@ -166,7 +166,7 @@ describe('getAll', () => { mockEsClient.mockReturnValue(mockEsSearchResult); await getPings({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateRange: { from: 'now-1h', to: 'now' }, size: 12, }); @@ -220,7 +220,7 @@ describe('getAll', () => { mockEsClient.mockReturnValue(mockEsSearchResult); await getPings({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateRange: { from: 'now-1h', to: 'now' }, sort: 'desc', }); @@ -274,7 +274,7 @@ describe('getAll', () => { mockEsClient.mockReturnValue(mockEsSearchResult); await getPings({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateRange: { from: 'now-1h', to: 'now' }, monitorId: 'testmonitorid', }); @@ -333,7 +333,7 @@ describe('getAll', () => { mockEsClient.mockReturnValue(mockEsSearchResult); await getPings({ callES: mockEsClient, - dynamicSettings: defaultDynamicSettings, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, dateRange: { from: 'now-1h', to: 'now' }, status: 'down', }); diff --git a/x-pack/plugins/uptime/server/lib/saved_objects.ts b/x-pack/plugins/uptime/server/lib/saved_objects.ts index 3ccfd498c44bf..d849fbd8ce0a8 100644 --- a/x-pack/plugins/uptime/server/lib/saved_objects.ts +++ b/x-pack/plugins/uptime/server/lib/saved_objects.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - DynamicSettings, - defaultDynamicSettings, -} from '../../../../legacy/plugins/uptime/common/runtime_types'; +import { DynamicSettings } from '../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../legacy/plugins/uptime/common/constants'; import { SavedObjectsType, SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import { UMSavedObjectsQueryFn } from './adapters'; @@ -28,12 +26,12 @@ export const umDynamicSettings: SavedObjectsType = { heartbeatIndices: { type: 'keyword', }, - certificatesThresholds: { + certThresholds: { properties: { - errorState: { + expiration: { type: 'long', }, - warningState: { + age: { type: 'long', }, }, @@ -49,7 +47,7 @@ export const savedObjectsAdapter: UMSavedObjectsAdapter = { return obj.attributes; } catch (getErr) { if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) { - return defaultDynamicSettings; + return DYNAMIC_SETTINGS_DEFAULTS; } throw getErr; } diff --git a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts index a1b731169f0a0..ea980721b831b 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts @@ -5,8 +5,10 @@ */ import expect from '@kbn/expect'; +import { isRight } from 'fp-ts/lib/Either'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { defaultDynamicSettings } from '../../../../../legacy/plugins/uptime/common/runtime_types/dynamic_settings'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants'; +import { DynamicSettingsType } from '../../../../../legacy/plugins/uptime/common/runtime_types'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -14,15 +16,16 @@ export default function({ getService }: FtrProviderContext) { describe('dynamic settings', () => { it('returns the defaults when no user settings have been saved', async () => { const apiResponse = await supertest.get(`/api/uptime/dynamic_settings`); - expect(apiResponse.body).to.eql(defaultDynamicSettings as any); + expect(apiResponse.body).to.eql(DYNAMIC_SETTINGS_DEFAULTS); + expect(isRight(DynamicSettingsType.decode(apiResponse.body))).to.be.ok(); }); it('can change the settings', async () => { const newSettings = { heartbeatIndices: 'myIndex1*', - certificatesThresholds: { - errorState: 5, - warningState: 15, + certThresholds: { + expiration: 5, + age: 15, }, }; const postResponse = await supertest @@ -35,6 +38,7 @@ export default function({ getService }: FtrProviderContext) { const getResponse = await supertest.get(`/api/uptime/dynamic_settings`); expect(getResponse.body).to.eql(newSettings); + expect(isRight(DynamicSettingsType.decode(getResponse.body))).to.be.ok(); }); }); } diff --git a/x-pack/test/functional/apps/uptime/settings.ts b/x-pack/test/functional/apps/uptime/settings.ts index e81bbc5ae42f9..64b6300e0df63 100644 --- a/x-pack/test/functional/apps/uptime/settings.ts +++ b/x-pack/test/functional/apps/uptime/settings.ts @@ -6,10 +6,8 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { - defaultDynamicSettings, - DynamicSettings, -} from '../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../legacy/plugins/uptime/common/constants'; +import { DynamicSettings } from '../../../../legacy/plugins/uptime/common/runtime_types'; import { makeChecks } from '../../../api_integration/apis/uptime/rest/helper/make_checks'; export default ({ getPageObjects, getService }: FtrProviderContext) => { @@ -32,7 +30,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await settings.go(); const fields = await settings.loadFields(); - expect(fields).to.eql(defaultDynamicSettings); + expect(fields).to.eql(DYNAMIC_SETTINGS_DEFAULTS); }); it('should disable the apply button when invalid or unchanged', async () => { @@ -62,7 +60,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await settings.go(); - const newFieldValues: DynamicSettings = { heartbeatIndices: 'new*' }; + const newFieldValues: DynamicSettings = { + heartbeatIndices: 'new*', + certThresholds: { + age: 365, + expiration: 30, + }, + }; await settings.changeHeartbeatIndicesInput(newFieldValues.heartbeatIndices); await settings.apply(); @@ -91,7 +95,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify that the settings page shows the value we previously saved await settings.go(); const fields = await settings.loadFields(); - expect(fields.certificatesThresholds.errorState).to.eql(newErrorThreshold); + expect(fields.certThresholds?.expiration).to.eql(newErrorThreshold); }); it('changing certificate expiration warning threshold is reflected in settings page', async () => { @@ -108,7 +112,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify that the settings page shows the value we previously saved await settings.go(); const fields = await settings.loadFields(); - expect(fields.certificatesThresholds.warningState).to.eql(newWarningThreshold); + expect(fields.certThresholds?.age).to.eql(newWarningThreshold); }); }); }; diff --git a/x-pack/test/functional/services/uptime/settings.ts b/x-pack/test/functional/services/uptime/settings.ts index 14cab368b766a..9719152b62d35 100644 --- a/x-pack/test/functional/services/uptime/settings.ts +++ b/x-pack/test/functional/services/uptime/settings.ts @@ -5,6 +5,7 @@ */ import { FtrProviderContext } from '../../ftr_provider_context'; +import { DynamicSettings } from '../../../../legacy/plugins/uptime/common/runtime_types'; export function UptimeSettingsProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); @@ -25,24 +26,24 @@ export function UptimeSettingsProvider({ getService }: FtrProviderContext) { await changeInputField(text, 'heartbeat-indices-input-loaded'); }, changeErrorThresholdInput: async (text: string) => { - await changeInputField(text, 'error-state-threshold-input-loaded'); + await changeInputField(text, 'expiration-threshold-input-loaded'); }, changeWarningThresholdInput: async (text: string) => { - await changeInputField(text, 'warning-state-threshold-input-loaded'); + await changeInputField(text, 'age-threshold-input-loaded'); }, - loadFields: async () => { + loadFields: async (): Promise => { const indInput = await testSubjects.find('heartbeat-indices-input-loaded', 5000); - const errorInput = await testSubjects.find('error-state-threshold-input-loaded', 5000); - const warningInput = await testSubjects.find('warning-state-threshold-input-loaded', 5000); + const expirationInput = await testSubjects.find('expiration-threshold-input-loaded', 5000); + const ageInput = await testSubjects.find('age-threshold-input-loaded', 5000); const heartbeatIndices = await indInput.getAttribute('value'); - const errorThreshold = await errorInput.getAttribute('value'); - const warningThreshold = await warningInput.getAttribute('value'); + const expiration = await expirationInput.getAttribute('value'); + const age = await ageInput.getAttribute('value'); return { heartbeatIndices, - certificatesThresholds: { - errorState: errorThreshold, - warningState: warningThreshold, + certThresholds: { + age: parseInt(age, 10), + expiration: parseInt(expiration, 10), }, }; }, From cbf006c2a0b7919c870976638fb71268c7901672 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Fri, 24 Apr 2020 19:20:15 +0200 Subject: [PATCH 13/13] Bump handlebars dependency from 4.5.3 to 4.7.6 (#64402) --- package.json | 2 +- x-pack/package.json | 2 +- yarn.lock | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 9bb308d4cdcf1..956ad9ee4f71d 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,7 @@ "glob-all": "^3.2.1", "globby": "^8.0.1", "h2o2": "^8.1.2", - "handlebars": "4.5.3", + "handlebars": "4.7.6", "hapi": "^17.5.3", "hapi-auth-cookie": "^9.0.0", "history": "^4.9.0", diff --git a/x-pack/package.json b/x-pack/package.json index 2a8827a1ed75b..d0025ff436649 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -250,7 +250,7 @@ "graphql-tag": "^2.9.2", "graphql-tools": "^3.0.2", "h2o2": "^8.1.2", - "handlebars": "4.5.3", + "handlebars": "4.7.6", "history": "4.9.0", "history-extra": "^5.0.1", "i18n-iso-countries": "^4.3.1", diff --git a/yarn.lock b/yarn.lock index a17102b301bb2..b9f80cd5923b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15611,14 +15611,15 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== -handlebars@4.5.3, handlebars@^4.0.1, handlebars@^4.1.2: - version "4.5.3" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482" - integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA== +handlebars@4.7.6, handlebars@^4.0.1, handlebars@^4.1.2: + version "4.7.6" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" + integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== dependencies: + minimist "^1.2.5" neo-async "^2.6.0" - optimist "^0.6.1" source-map "^0.6.1" + wordwrap "^1.0.0" optionalDependencies: uglify-js "^3.1.4"