From bdc87409ba3c376fb964983f22b2a6405e8828e9 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Wed, 16 Jun 2021 10:35:55 -0400 Subject: [PATCH 01/15] [Lens] Create mathColumn function to improve performance (#101908) * [Lens] Create mathColumn function to improve performance * Fix empty formula case * Fix tinymath memoization Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../canvas/canvas-function-reference.asciidoc | 57 +++++++-- packages/kbn-tinymath/src/index.js | 9 +- .../expression_functions/specs/index.ts | 1 + .../expression_functions/specs/math_column.ts | 111 ++++++++++++++++++ .../specs/tests/math_column.test.ts | 74 ++++++++++++ .../common/service/expressions_services.ts | 2 + .../definitions/formula/formula.tsx | 21 +--- .../operations/definitions/formula/math.tsx | 19 +-- 8 files changed, 248 insertions(+), 46 deletions(-) create mode 100644 src/plugins/expressions/common/expression_functions/specs/math_column.ts create mode 100644 src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index 272cd524c2c200..ac7cbba6e9933a 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -71,7 +71,7 @@ Alias: `condition` [[alterColumn_fn]] === `alterColumn` -Converts between core types, including `string`, `number`, `null`, `boolean`, and `date`, and renames columns. See also <> and <>. +Converts between core types, including `string`, `number`, `null`, `boolean`, and `date`, and renames columns. See also <>, <>, and <>. *Expression syntax* [source,js] @@ -1717,11 +1717,16 @@ Adds a column calculated as the result of other columns. Changes are made only w |=== |Argument |Type |Description +|`id` + +|`string`, `null` +|An optional id of the resulting column. When no id is provided, the id will be looked up from the existing column by the provided name argument. If no column with this name exists yet, a new column with this name and an identical id will be added to the table. + |_Unnamed_ *** Aliases: `column`, `name` |`string` -|The name of the resulting column. +|The name of the resulting column. Names are not required to be unique. |`expression` *** @@ -1729,11 +1734,6 @@ Aliases: `exp`, `fn`, `function` |`boolean`, `number`, `string`, `null` |A Canvas expression that is passed to each row as a single row `datatable`. -|`id` - -|`string`, `null` -|An optional id of the resulting column. When not specified or `null` the name argument is used as id. - |`copyMetaFrom` |`string`, `null` @@ -1808,6 +1808,47 @@ Default: `"throw"` *Returns:* `number` | `boolean` | `null` +[float] +[[mathColumn_fn]] +=== `mathColumn` + +Adds a column by evaluating `TinyMath` on each row. This function is optimized for math, so it performs better than the <> with a <>. +*Accepts:* `datatable` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|id *** +|`string` +|id of the resulting column. Must be unique. + +|name *** +|`string` +|The name of the resulting column. Names are not required to be unique. + +|_Unnamed_ + +Alias: `expression` +|`string` +|A `TinyMath` expression evaluated on each row. See https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html. + +|`onError` + +|`string` +|In case the `TinyMath` evaluation fails or returns NaN, the return value is specified by onError. For example, `"null"`, `"zero"`, `"false"`, `"throw"`. When `"throw"`, it will throw an exception, terminating expression execution. + +Default: `"throw"` + +|`copyMetaFrom` + +|`string`, `null` +|If set, the meta object from the specified column id is copied over to the specified target column. Throws an exception if the column doesn't exist +|=== + +*Returns:* `datatable` + + [float] [[metric_fn]] === `metric` @@ -2581,7 +2622,7 @@ Default: `false` [[staticColumn_fn]] === `staticColumn` -Adds a column with the same static value in every row. See also <> and <>. +Adds a column with the same static value in every row. See also <>, <>, and <>. *Accepts:* `datatable` diff --git a/packages/kbn-tinymath/src/index.js b/packages/kbn-tinymath/src/index.js index 9f1bb7b8514634..6fde4c202e2a77 100644 --- a/packages/kbn-tinymath/src/index.js +++ b/packages/kbn-tinymath/src/index.js @@ -7,12 +7,11 @@ */ const { get } = require('lodash'); +const memoizeOne = require('memoize-one'); // eslint-disable-next-line import/no-unresolved const { parse: parseFn } = require('../grammar'); const { functions: includedFunctions } = require('./functions'); -module.exports = { parse, evaluate, interpret }; - function parse(input, options) { if (input == null) { throw new Error('Missing expression'); @@ -29,9 +28,11 @@ function parse(input, options) { } } +const memoizedParse = memoizeOne(parse); + function evaluate(expression, scope = {}, injectedFunctions = {}) { scope = scope || {}; - return interpret(parse(expression), scope, injectedFunctions); + return interpret(memoizedParse(expression), scope, injectedFunctions); } function interpret(node, scope, injectedFunctions) { @@ -79,3 +80,5 @@ function isOperable(args) { return typeof arg === 'number' && !isNaN(arg); }); } + +module.exports = { parse: memoizedParse, evaluate, interpret }; diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index c6d89f41d0e0d3..e808021f751800 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -18,3 +18,4 @@ export * from './moving_average'; export * from './ui_setting'; export { mapColumn, MapColumnArguments } from './map_column'; export { math, MathArguments, MathInput } from './math'; +export { mathColumn, MathColumnArguments } from './math_column'; diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts new file mode 100644 index 00000000000000..0ff8faf3ce55a1 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/math_column.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../types'; +import { math, MathArguments } from './math'; +import { Datatable, DatatableColumn, getType } from '../../expression_types'; + +export type MathColumnArguments = MathArguments & { + id: string; + name?: string; + copyMetaFrom?: string | null; +}; + +export const mathColumn: ExpressionFunctionDefinition< + 'mathColumn', + Datatable, + MathColumnArguments, + Datatable +> = { + name: 'mathColumn', + type: 'datatable', + inputTypes: ['datatable'], + help: i18n.translate('expressions.functions.mathColumnHelpText', { + defaultMessage: + 'Adds a column calculated as the result of other columns. ' + + 'Changes are made only when you provide arguments.' + + 'See also {alterColumnFn} and {staticColumnFn}.', + values: { + alterColumnFn: '`alterColumn`', + staticColumnFn: '`staticColumn`', + }, + }), + args: { + ...math.args, + id: { + types: ['string'], + help: i18n.translate('expressions.functions.mathColumn.args.idHelpText', { + defaultMessage: 'id of the resulting column. Must be unique.', + }), + required: true, + }, + name: { + types: ['string'], + aliases: ['_', 'column'], + help: i18n.translate('expressions.functions.mathColumn.args.nameHelpText', { + defaultMessage: 'The name of the resulting column. Names are not required to be unique.', + }), + required: true, + }, + copyMetaFrom: { + types: ['string', 'null'], + help: i18n.translate('expressions.functions.mathColumn.args.copyMetaFromHelpText', { + defaultMessage: + "If set, the meta object from the specified column id is copied over to the specified target column. If the column doesn't exist it silently fails.", + }), + required: false, + default: null, + }, + }, + fn: (input, args, context) => { + const columns = [...input.columns]; + const existingColumnIndex = columns.findIndex(({ id }) => { + return id === args.id; + }); + if (existingColumnIndex > -1) { + throw new Error('ID must be unique'); + } + + const newRows = input.rows.map((row) => { + return { + ...row, + [args.id]: math.fn( + { + type: 'datatable', + columns: input.columns, + rows: [row], + }, + { + expression: args.expression, + onError: args.onError, + }, + context + ), + }; + }); + const type = newRows.length ? getType(newRows[0][args.id]) : 'null'; + const newColumn: DatatableColumn = { + id: args.id, + name: args.name ?? args.id, + meta: { type, params: { id: type } }, + }; + if (args.copyMetaFrom) { + const metaSourceFrom = columns.find(({ id }) => id === args.copyMetaFrom); + newColumn.meta = { ...newColumn.meta, ...(metaSourceFrom?.meta || {}) }; + } + + columns.push(newColumn); + + return { + type: 'datatable', + columns, + rows: newRows, + } as Datatable; + }, +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts new file mode 100644 index 00000000000000..bc6699a2b689bf --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { mathColumn } from '../math_column'; +import { functionWrapper, testTable } from './utils'; + +describe('mathColumn', () => { + const fn = functionWrapper(mathColumn); + + it('throws if the id is used', () => { + expect(() => fn(testTable, { id: 'price', name: 'price', expression: 'price * 2' })).toThrow( + `ID must be unique` + ); + }); + + it('applies math to each row by id', () => { + const result = fn(testTable, { id: 'output', name: 'output', expression: 'quantity * price' }); + expect(result.columns).toEqual([ + ...testTable.columns, + { id: 'output', name: 'output', meta: { params: { id: 'number' }, type: 'number' } }, + ]); + expect(result.rows[0]).toEqual({ + in_stock: true, + name: 'product1', + output: 60500, + price: 605, + quantity: 100, + time: 1517842800950, + }); + }); + + it('handles onError', () => { + const args = { + id: 'output', + name: 'output', + expression: 'quantity / 0', + }; + expect(() => fn(testTable, args)).toThrowError(`Cannot divide by 0`); + expect(() => fn(testTable, { ...args, onError: 'throw' })).toThrow(); + expect(fn(testTable, { ...args, onError: 'zero' }).rows[0].output).toEqual(0); + expect(fn(testTable, { ...args, onError: 'false' }).rows[0].output).toEqual(false); + expect(fn(testTable, { ...args, onError: 'null' }).rows[0].output).toEqual(null); + }); + + it('should copy over the meta information from the specified column', async () => { + const result = await fn( + { + ...testTable, + columns: [ + ...testTable.columns, + { + id: 'myId', + name: 'myName', + meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, + }, + ], + rows: testTable.rows.map((row) => ({ ...row, myId: Date.now() })), + }, + { id: 'output', name: 'name', copyMetaFrom: 'myId', expression: 'price + 2' } + ); + + expect(result.type).toBe('datatable'); + expect(result.columns[result.columns.length - 1]).toEqual({ + id: 'output', + name: 'name', + meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, + }); + }); +}); diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index f7afc12aa96bad..b3c01672626614 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -31,6 +31,7 @@ import { mapColumn, overallMetric, math, + mathColumn, } from '../expression_functions'; /** @@ -344,6 +345,7 @@ export class ExpressionsService implements PersistableStateService Date: Wed, 16 Jun 2021 11:02:55 -0400 Subject: [PATCH 02/15] [Watcher] Migrate to use new page layout (#101956) --- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../watcher/public/application/app.tsx | 44 +++--- .../components/page_error/page_error.tsx | 2 +- .../page_error/page_error_forbidden.tsx | 3 +- .../page_error/page_error_not_exist.tsx | 22 ++- .../json_watch_edit/json_watch_edit.tsx | 64 +++----- .../monitoring_watch_edit.tsx | 56 ++----- .../threshold_watch_edit.tsx | 27 ++-- .../watch_edit/components/watch_edit.tsx | 53 +++---- .../watch_list/components/watch_list.tsx | 149 ++++++++---------- .../watch_status/components/watch_status.tsx | 146 ++++++++--------- .../public/application/shared_imports.ts | 1 + 13 files changed, 262 insertions(+), 309 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fcb5710f8208f9..fb936a58387816 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -24295,8 +24295,6 @@ "xpack.watcher.sections.watchEdit.json.titlePanel.editWatchTitle": "{watchName}を編集", "xpack.watcher.sections.watchEdit.loadingWatchDescription": "ウォッチの読み込み中…", "xpack.watcher.sections.watchEdit.loadingWatchVisualizationDescription": "ウォッチビジュアライゼーションを読み込み中…", - "xpack.watcher.sections.watchEdit.monitoring.edit.calloutDescriptionText": "ウォッチ'{watchName}'はシステムウォッチであるため、編集できません。{watchStatusLink}", - "xpack.watcher.sections.watchEdit.monitoring.edit.calloutTitleText": "このウォッチは編集できません。", "xpack.watcher.sections.watchEdit.monitoring.header.watchLinkTitle": "ウォッチステータスを表示します。", "xpack.watcher.sections.watchEdit.simulate.form.actionModesFieldLabel": "アクションモード", "xpack.watcher.sections.watchEdit.simulate.form.actionOverridesDescription": "ウォッチでアクションを実行またはスキップすることができるようにします。{actionsLink}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8c865b62d9d1ab..998b2a4c672872 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -24665,8 +24665,6 @@ "xpack.watcher.sections.watchEdit.json.titlePanel.editWatchTitle": "编辑 {watchName}", "xpack.watcher.sections.watchEdit.loadingWatchDescription": "正在加载监视……", "xpack.watcher.sections.watchEdit.loadingWatchVisualizationDescription": "正在加载监视可视化……", - "xpack.watcher.sections.watchEdit.monitoring.edit.calloutDescriptionText": "监视“{watchName}”为系统监视,无法编辑。{watchStatusLink}", - "xpack.watcher.sections.watchEdit.monitoring.edit.calloutTitleText": "此监视无法编辑。", "xpack.watcher.sections.watchEdit.monitoring.header.watchLinkTitle": "查看监视状态。", "xpack.watcher.sections.watchEdit.simulate.form.actionModesFieldLabel": "操作模式", "xpack.watcher.sections.watchEdit.simulate.form.actionOverridesDescription": "允许监视执行或跳过操作。{actionsLink}", diff --git a/x-pack/plugins/watcher/public/application/app.tsx b/x-pack/plugins/watcher/public/application/app.tsx index 6c233a44830b54..98d10bce3b6b24 100644 --- a/x-pack/plugins/watcher/public/application/app.tsx +++ b/x-pack/plugins/watcher/public/application/app.tsx @@ -17,7 +17,7 @@ import { import { Router, Switch, Route, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; -import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { EuiPageContent, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -62,24 +62,30 @@ export const App = (deps: AppDeps) => { if (!valid) { return ( - - } - color="danger" - iconType="help" - > - {message}{' '} - - - - + + + + + } + body={

{message}

} + actions={[ + + + , + ]} + /> +
); } return ( diff --git a/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx b/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx index ca05d390518f26..321b5c0e5e11b7 100644 --- a/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx +++ b/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx @@ -25,7 +25,7 @@ export function getPageErrorCode(errorOrErrors: any) { } } -export function PageError({ errorCode, id }: { errorCode?: any; id?: any }) { +export function PageError({ errorCode, id }: { errorCode?: number; id?: string }) { switch (errorCode) { case 404: return ; diff --git a/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx b/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx index c2e93c7f066001..56dc5c7dc22b53 100644 --- a/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx +++ b/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx @@ -13,8 +13,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; export function PageErrorForbidden() { return ( - + {id ? ( + + ) : ( + + )}

} /> diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx index 8b5827fbd0fe0d..80931c3f60c05a 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx @@ -7,15 +7,7 @@ import React, { useContext, useState } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiPageContent, - EuiSpacer, - EuiTab, - EuiTabs, - EuiTitle, -} from '@elastic/eui'; +import { EuiPageHeader, EuiSpacer, EuiPageContentBody } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ExecuteDetails } from '../../../../models/execute_details'; import { getActionType } from '../../../../../../common/lib/get_action_type'; @@ -96,36 +88,31 @@ export const JsonWatchEdit = ({ pageTitle }: { pageTitle: string }) => { const hasExecuteWatchErrors = !!Object.keys(executeWatchErrors).find( (errorKey) => executeWatchErrors[errorKey].length >= 1 ); + return ( - - - - -

{pageTitle}

-
-
-
- - {WATCH_TABS.map((tab, index) => ( - { - setSelectedTab(tab.id); - setExecuteDetails( - new ExecuteDetails({ - ...executeDetails, - actionModes: getActionModes(watchActions), - }) - ); - }} - isSelected={tab.id === selectedTab} - key={index} - data-test-subj="tab" - > - {tab.name} - - ))} - + + {pageTitle}} + bottomBorder + tabs={WATCH_TABS.map((tab, index) => ({ + onClick: () => { + setSelectedTab(tab.id); + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + actionModes: getActionModes(watchActions), + }) + ); + }, + isSelected: tab.id === selectedTab, + key: index, + 'data-test-subj': 'tab', + label: tab.name, + }))} + /> + + {selectedTab === WATCH_SIMULATE_TAB && ( { watchActions={watchActions} /> )} + {selectedTab === WATCH_EDIT_TAB && } -
+ ); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx index 930c11340ce5e3..b00e4dc310e27e 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx @@ -7,16 +7,7 @@ import React, { useContext } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiPageContent, - EuiSpacer, - EuiTitle, - EuiCallOut, - EuiText, - EuiLink, -} from '@elastic/eui'; +import { EuiPageContent, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { WatchContext } from '../../watch_context'; import { useAppContext } from '../../../../app_context'; @@ -27,46 +18,31 @@ export const MonitoringWatchEdit = ({ pageTitle }: { pageTitle: string }) => { const { watch } = useContext(WatchContext); const { history } = useAppContext(); - const systemWatchTitle = ( - - ); - const systemWatchMessage = ( - - - ), }} /> ); return ( - - - - -

{pageTitle}

-
-
-
- - - -

{systemWatchMessage}

-
-
+ + {pageTitle}} + body={

{systemWatchMessage}

} + actions={[ + + + , + ]} + />
); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 2f89a3bc2be641..6587974363a802 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -18,13 +18,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiForm, - EuiPageContent, EuiPopover, EuiPopoverTitle, EuiSelect, EuiSpacer, EuiText, EuiTitle, + EuiPageHeader, + EuiPageContentBody, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -236,19 +237,15 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { }; return ( - - - - -

{pageTitle}

-
- - - {watch.titleDescription} - -
-
- + + {pageTitle}} + description={watch.titleDescription} + bottomBorder + /> + + + {serverError && ( @@ -957,6 +954,6 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { close={() => setIsRequestVisible(false)} /> ) : null} -
+ ); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx index 525ae077df655f..fa3c7e374f7b56 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx @@ -10,19 +10,20 @@ import { isEqual } from 'lodash'; import { EuiPageContent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - import { FormattedMessage } from '@kbn/i18n/react'; -import { Watch } from '../../../models/watch'; + import { WATCH_TYPES } from '../../../../../common/constants'; import { BaseWatch } from '../../../../../common/types/watch_types'; -import { getPageErrorCode, PageError, SectionLoading, SectionError } from '../../../components'; +import { getPageErrorCode, PageError, SectionLoading } from '../../../components'; import { loadWatch } from '../../../lib/api'; import { listBreadcrumb, editBreadcrumb, createBreadcrumb } from '../../../lib/breadcrumbs'; +import { useAppContext } from '../../../app_context'; +import { Watch } from '../../../models/watch'; +import { PageError as GenericPageError } from '../../../shared_imports'; +import { WatchContext } from '../watch_context'; import { JsonWatchEdit } from './json_watch_edit'; import { ThresholdWatchEdit } from './threshold_watch_edit'; import { MonitoringWatchEdit } from './monitoring_watch_edit'; -import { WatchContext } from '../watch_context'; -import { useAppContext } from '../../../app_context'; const getTitle = (watch: BaseWatch) => { if (watch.isNew) { @@ -115,7 +116,7 @@ export const WatchEdit = ({ const loadedWatch = await loadWatch(id); dispatch({ command: 'setWatch', payload: loadedWatch }); } catch (error) { - dispatch({ command: 'setError', payload: error }); + dispatch({ command: 'setError', payload: error.body }); } } else if (type) { const WatchType = Watch.getWatchTypes()[type]; @@ -135,36 +136,34 @@ export const WatchEdit = ({ const errorCode = getPageErrorCode(loadError); if (errorCode) { return ( - + ); - } - - if (loadError) { + } else if (loadError) { return ( - - - } - error={loadError} - /> - + + } + error={loadError} + /> ); } if (!watch) { return ( - - - + + + + + ); } diff --git a/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx index 0e89871063507e..31accef0b63691 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx @@ -11,25 +11,25 @@ import { CriteriaWithPagination, EuiButton, EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, EuiInMemoryTable, EuiLink, EuiPageContent, EuiSpacer, EuiText, - EuiTitle, EuiToolTip, EuiEmptyPrompt, EuiButtonIcon, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem, + EuiPageHeader, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Moment } from 'moment'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + import { REFRESH_INTERVALS, PAGINATION, WATCH_TYPES } from '../../../../../common/constants'; import { listBreadcrumb } from '../../../lib/breadcrumbs'; import { @@ -37,15 +37,13 @@ import { PageError, DeleteWatchesModal, WatchStatus, - SectionError, SectionLoading, Error, } from '../../../components'; import { useLoadWatches } from '../../../lib/api'; import { goToCreateThresholdAlert, goToCreateAdvancedWatch } from '../../../lib/navigation'; import { useAppContext } from '../../../app_context'; - -import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; +import { PageError as GenericPageError } from '../../../shared_imports'; export const WatchList = () => { // hooks @@ -173,21 +171,36 @@ export const WatchList = () => { if (isWatchesLoading) { return ( - - - + + + + + ); } - if (getPageErrorCode(error)) { + const errorCode = getPageErrorCode(error); + if (errorCode) { return ( - - + + ); + } else if (error) { + return ( + + } + error={(error as unknown) as Error} + /> + ); } if (availableWatches && availableWatches.length === 0) { @@ -206,7 +219,7 @@ export const WatchList = () => { ); return ( - + { let content; - if (error) { - content = ( - - } - error={(error as unknown) as Error} - /> - ); - } else if (availableWatches) { + if (availableWatches) { const columns = [ { field: 'id', @@ -463,56 +464,46 @@ export const WatchList = () => { ); } - if (content) { - return ( - - { - if (deleted) { - setDeletedWatches([...deletedWatches, ...watchesToDelete]); - } - setWatchesToDelete([]); - }} - watchesToDelete={watchesToDelete} - /> - - - - -

- -

-
- - - - - -
-
- - - - -

{watcherDescriptionText}

-
+ return ( + <> + + + + } + bottomBorder + rightSideItems={[ + + + , + ]} + description={watcherDescriptionText} + /> + { + if (deleted) { + setDeletedWatches([...deletedWatches, ...watchesToDelete]); + } + setWatchesToDelete([]); + }} + watchesToDelete={watchesToDelete} + /> - + - {content} -
- ); - } - return null; + {content} + + ); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx b/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx index 1e3548620339aa..73400b9ccaaa72 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx @@ -9,14 +9,10 @@ import React, { useEffect, useState } from 'react'; import { EuiPageContent, EuiSpacer, - EuiTabs, - EuiTab, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, EuiToolTip, EuiBadge, EuiButtonEmpty, + EuiPageHeader, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -88,18 +84,20 @@ export const WatchStatus = ({ if (isWatchDetailLoading) { return ( - - - + + + + + ); } if (errorCode) { return ( - + ); @@ -156,20 +154,11 @@ export const WatchStatus = ({ return ( - - { - if (deleted) { - goToWatchList(); - } - setWatchesToDelete([]); - }} - watchesToDelete={watchesToDelete} - /> - - - -

+ <> + + -

-
-
- {isSystemWatch ? ( - - - } - > - - - - - - ) : ( - - - + + {isSystemWatch && ( + <> + {' '} + + } + > + + + + + + )} + + } + bottomBorder + rightSideItems={ + isSystemWatch + ? [] + : [ toggleWatchActivation()} isLoading={isTogglingActivation} > {activationButtonText} - - - + , { @@ -223,30 +213,34 @@ export const WatchStatus = ({ id="xpack.watcher.sections.watchHistory.deleteWatchButtonLabel" defaultMessage="Delete" /> - - - - - )} -
- - - {WATCH_STATUS_TABS.map((tab, index) => ( - { - setSelectedTab(tab.id); - }} - isSelected={tab.id === selectedTab} - key={index} - data-test-subj="tab" - > - {tab.name} - - ))} - + , + ] + } + tabs={WATCH_STATUS_TABS.map((tab, index) => ({ + onClick: () => { + setSelectedTab(tab.id); + }, + isSelected: tab.id === selectedTab, + key: index, + 'data-test-subj': 'tab', + label: tab.name, + }))} + /> + + {selectedTab === WATCH_ACTIONS_TAB ? : } -
+ + { + if (deleted) { + goToWatchList(); + } + setWatchesToDelete([]); + }} + watchesToDelete={watchesToDelete} + /> +
); } diff --git a/x-pack/plugins/watcher/public/application/shared_imports.ts b/x-pack/plugins/watcher/public/application/shared_imports.ts index e3eb11eda77b30..44bef3b0c4f5f6 100644 --- a/x-pack/plugins/watcher/public/application/shared_imports.ts +++ b/x-pack/plugins/watcher/public/application/shared_imports.ts @@ -12,4 +12,5 @@ export { sendRequest, useRequest, XJson, + PageError, } from '../../../../../src/plugins/es_ui_shared/public'; From 8abb656d7f929c2a93b143b9e87f6ddc00d1f3a2 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 16 Jun 2021 18:15:47 +0300 Subject: [PATCH 03/15] [Kuery] Move json utils (#102058) * Move JSON utils to utils package * Imports from tests * delete * split package * docs * test * test * imports Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...bana-plugin-plugins-data-public.eskuery.md | 2 +- ...bana-plugin-plugins-data-server.eskuery.md | 2 +- package.json | 1 + packages/BUILD.bazel | 1 + packages/kbn-common-utils/BUILD.bazel | 82 +++++++++++++++++++ packages/kbn-common-utils/README.md | 3 + packages/kbn-common-utils/jest.config.js | 13 +++ packages/kbn-common-utils/package.json | 9 ++ packages/kbn-common-utils/src/index.ts | 9 ++ packages/kbn-common-utils/src/json/index.ts | 9 ++ .../kbn-common-utils/src/json}/typed_json.ts | 0 packages/kbn-common-utils/tsconfig.json | 18 ++++ .../data/common/es_query/kuery/ast/ast.ts | 2 +- .../es_query/kuery/node_types/named_arg.ts | 2 +- .../common/es_query/kuery/node_types/types.ts | 2 +- src/plugins/data/public/public.api.md | 3 +- src/plugins/data/server/server.api.md | 3 +- src/plugins/kibana_utils/common/index.ts | 1 - src/plugins/kibana_utils/public/index.ts | 3 - .../alerting/common/alert_navigation.ts | 3 +- .../public/alert_navigation_registry/types.ts | 2 +- .../authorization/alerting_authorization.ts | 2 +- .../alerting_authorization_kuery.ts | 2 +- .../server/routes/lib/rewrite_request_case.ts | 2 +- .../graph/public/types/workspace_state.ts | 2 +- x-pack/plugins/infra/common/typed_json.ts | 2 +- .../components/log_stream/log_stream.tsx | 2 +- .../logging/log_text_stream/field_value.tsx | 2 +- .../log_entry_field_column.tsx | 2 +- .../utils/log_column_render_configuration.tsx | 2 +- .../lib/adapters/framework/adapter_types.ts | 2 +- .../log_entries/kibana_log_entries_adapter.ts | 2 +- .../log_entries_domain/log_entries_domain.ts | 3 +- .../snapshot/lib/get_metrics_aggregations.ts | 2 +- .../services/log_entries/message/message.ts | 2 +- .../log_entries/message/rule_types.ts | 2 +- .../infra/server/utils/serialized_query.ts | 2 +- .../server/utils/typed_search_strategy.ts | 2 +- x-pack/plugins/ml/common/types/es_client.ts | 2 +- x-pack/plugins/osquery/common/typed_json.ts | 2 +- .../security_solution/common/typed_json.ts | 2 +- .../public/common/lib/keury/index.ts | 2 +- .../routes/resolver/queries/events.ts | 2 +- .../resolver/tree/queries/descendants.ts | 2 +- .../routes/resolver/tree/queries/lifecycle.ts | 2 +- .../routes/resolver/tree/queries/stats.ts | 2 +- .../routes/resolver/utils/pagination.ts | 2 +- .../server/endpoint/types.ts | 2 +- .../server/utils/serialized_query.ts | 2 +- .../server/monitoring/capacity_estimation.ts | 2 +- .../monitoring_stats_stream.test.ts | 2 +- .../monitoring/monitoring_stats_stream.ts | 2 +- .../runtime_statistics_aggregator.ts | 2 +- .../server/monitoring/task_run_calcultors.ts | 2 +- .../server/monitoring/task_run_statistics.ts | 2 +- .../server/monitoring/workload_statistics.ts | 2 +- .../task_manager/server/routes/health.ts | 2 +- .../uptime/server/lib/alerts/status_check.ts | 2 +- .../server/lib/requests/get_monitor_status.ts | 2 +- .../tests/services/annotations.ts | 2 +- .../basic/tests/annotations.ts | 2 +- .../trial/tests/annotations.ts | 2 +- .../apis/resolver/events.ts | 2 +- yarn.lock | 4 + 64 files changed, 203 insertions(+), 56 deletions(-) create mode 100644 packages/kbn-common-utils/BUILD.bazel create mode 100644 packages/kbn-common-utils/README.md create mode 100644 packages/kbn-common-utils/jest.config.js create mode 100644 packages/kbn-common-utils/package.json create mode 100644 packages/kbn-common-utils/src/index.ts create mode 100644 packages/kbn-common-utils/src/json/index.ts rename {src/plugins/kibana_utils/common => packages/kbn-common-utils/src/json}/typed_json.ts (100%) create mode 100644 packages/kbn-common-utils/tsconfig.json diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md index 5d92e348d62760..2cde2b74555851 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md @@ -10,6 +10,6 @@ esKuery: { nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes; fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode; - toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject; + toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md index 19cb742785e7b2..4b96d8af756f37 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md @@ -10,6 +10,6 @@ esKuery: { nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes; fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode; - toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject; + toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; } ``` diff --git a/package.json b/package.json index c9c6fa7f582c59..596bcff59797d8 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "@kbn/ui-framework": "link:packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types", + "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/utils": "link:bazel-bin/packages/kbn-utils", "@loaders.gl/core": "^2.3.1", "@loaders.gl/json": "^2.3.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 3e17d471a3cac0..f2510a2386aa2c 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -12,6 +12,7 @@ filegroup( "//packages/kbn-apm-utils:build", "//packages/kbn-babel-code-parser:build", "//packages/kbn-babel-preset:build", + "//packages/kbn-common-utils:build", "//packages/kbn-config:build", "//packages/kbn-config-schema:build", "//packages/kbn-crypto:build", diff --git a/packages/kbn-common-utils/BUILD.bazel b/packages/kbn-common-utils/BUILD.bazel new file mode 100644 index 00000000000000..02446849733537 --- /dev/null +++ b/packages/kbn-common-utils/BUILD.bazel @@ -0,0 +1,82 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-common-utils" +PKG_REQUIRE_NAME = "@kbn/common-utils" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = ["**/*.test.*"], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/kbn-config-schema", + "@npm//load-json-file", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-common-utils/README.md b/packages/kbn-common-utils/README.md new file mode 100644 index 00000000000000..7b64c9f18fe89d --- /dev/null +++ b/packages/kbn-common-utils/README.md @@ -0,0 +1,3 @@ +# @kbn/common-utils + +Shared common (client and server sie) utilities shared across packages and plugins. \ No newline at end of file diff --git a/packages/kbn-common-utils/jest.config.js b/packages/kbn-common-utils/jest.config.js new file mode 100644 index 00000000000000..08f1995c474236 --- /dev/null +++ b/packages/kbn-common-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-common-utils'], +}; diff --git a/packages/kbn-common-utils/package.json b/packages/kbn-common-utils/package.json new file mode 100644 index 00000000000000..db99f4d6afb985 --- /dev/null +++ b/packages/kbn-common-utils/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/common-utils", + "main": "./target/index.js", + "browser": "./target/index.js", + "types": "./target/index.d.ts", + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "private": true +} \ No newline at end of file diff --git a/packages/kbn-common-utils/src/index.ts b/packages/kbn-common-utils/src/index.ts new file mode 100644 index 00000000000000..1b8bffe4bf1580 --- /dev/null +++ b/packages/kbn-common-utils/src/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './json'; diff --git a/packages/kbn-common-utils/src/json/index.ts b/packages/kbn-common-utils/src/json/index.ts new file mode 100644 index 00000000000000..96c94df1bb48eb --- /dev/null +++ b/packages/kbn-common-utils/src/json/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { JsonArray, JsonValue, JsonObject } from './typed_json'; diff --git a/src/plugins/kibana_utils/common/typed_json.ts b/packages/kbn-common-utils/src/json/typed_json.ts similarity index 100% rename from src/plugins/kibana_utils/common/typed_json.ts rename to packages/kbn-common-utils/src/json/typed_json.ts diff --git a/packages/kbn-common-utils/tsconfig.json b/packages/kbn-common-utils/tsconfig.json new file mode 100644 index 00000000000000..98f1b30c0d7ff2 --- /dev/null +++ b/packages/kbn-common-utils/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "incremental": true, + "outDir": "target", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-common-utils/src", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.ts b/src/plugins/data/common/es_query/kuery/ast/ast.ts index 5b22e3b3a3e0ea..be821289699689 100644 --- a/src/plugins/data/common/es_query/kuery/ast/ast.ts +++ b/src/plugins/data/common/es_query/kuery/ast/ast.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { JsonObject } from '@kbn/common-utils'; import { nodeTypes } from '../node_types/index'; import { KQLSyntaxError } from '../kuery_syntax_error'; import { KueryNode, DslQuery, KueryParseOptions } from '../types'; @@ -13,7 +14,6 @@ import { IIndexPattern } from '../../../index_patterns/types'; // @ts-ignore import { parse as parseKuery } from './_generated_/kuery'; -import { JsonObject } from '../../../../../kibana_utils/common'; const fromExpression = ( expression: string | DslQuery, diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts index c65f195040b185..b1b202e4323af7 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts @@ -7,10 +7,10 @@ */ import _ from 'lodash'; +import { JsonObject } from '@kbn/common-utils'; import * as ast from '../ast'; import { nodeTypes } from '../node_types'; import { NamedArgTypeBuildNode } from './types'; -import { JsonObject } from '../../../../../kibana_utils/common'; export function buildNode(name: string, value: any): NamedArgTypeBuildNode { const argumentNode = diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/src/plugins/data/common/es_query/kuery/node_types/types.ts index 196890ed0f7a3a..b3247a0ad8dc21 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/types.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/types.ts @@ -10,8 +10,8 @@ * WARNING: these typings are incomplete */ +import { JsonValue } from '@kbn/common-utils'; import { IIndexPattern } from '../../../index_patterns'; -import { JsonValue } from '../../../../../kibana_utils/common'; import { KueryNode } from '..'; export type FunctionName = diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 13352d183370bd..d56727b468da6f 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -53,6 +53,7 @@ import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiSettingsClient } from 'src/core/public'; +import { JsonValue } from '@kbn/common-utils'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; @@ -856,7 +857,7 @@ export const esFilters: { export const esKuery: { nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes; fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode; - toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject; + toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; }; // Warning: (ae-missing-release-tag) "esQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 783bd8d2fcd0e1..c2b533bc42dc6f 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -38,6 +38,7 @@ import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'src/core/server'; import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/server'; +import { JsonValue } from '@kbn/common-utils'; import { KibanaRequest } from 'src/core/server'; import { KibanaRequest as KibanaRequest_2 } from 'kibana/server'; import { Logger } from 'src/core/server'; @@ -460,7 +461,7 @@ export const esFilters: { export const esKuery: { nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes; fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode; - toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject; + toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; }; // Warning: (ae-missing-release-tag) "esQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index 76a7cb2855c6e0..773c0b96d64136 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -11,7 +11,6 @@ export * from './field_wildcard'; export * from './of'; export * from './ui'; export * from './state_containers'; -export * from './typed_json'; export * from './errors'; export { AbortError, abortSignalToPromise } from './abort_utils'; export { createGetterSetter, Get, Set } from './create_getter_setter'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 75c52e1301ea57..3d9b5db0629558 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -15,9 +15,6 @@ export { fieldWildcardFilter, fieldWildcardMatcher, Get, - JsonArray, - JsonObject, - JsonValue, of, Set, UiComponent, diff --git a/x-pack/plugins/alerting/common/alert_navigation.ts b/x-pack/plugins/alerting/common/alert_navigation.ts index d26afff9e8243f..7c9e428f9a09ee 100644 --- a/x-pack/plugins/alerting/common/alert_navigation.ts +++ b/x-pack/plugins/alerting/common/alert_navigation.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { JsonObject } from '../../../../src/plugins/kibana_utils/common'; - +import { JsonObject } from '@kbn/common-utils'; export interface AlertUrlNavigation { path: string; } diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts index 53540facd9652e..12ac9061426475 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { SanitizedAlert } from '../../common'; /** diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 7506accd8b88ea..52cef9a402e352 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -8,6 +8,7 @@ import Boom from '@hapi/boom'; import { map, mapValues, fromPairs, has } from 'lodash'; import { KibanaRequest } from 'src/core/server'; +import { JsonObject } from '@kbn/common-utils'; import { AlertTypeRegistry } from '../types'; import { SecurityPluginSetup } from '../../../security/server'; import { RegistryAlertType } from '../alert_type_registry'; @@ -19,7 +20,6 @@ import { AlertingAuthorizationFilterOpts, } from './alerting_authorization_kuery'; import { KueryNode } from '../../../../../src/plugins/data/server'; -import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; export enum AlertingAuthorizationEntity { Rule = 'rule', diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts index eb6f1605f2ba5a..5205e6afdf29f3 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts @@ -6,7 +6,7 @@ */ import { remove } from 'lodash'; -import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { nodeBuilder, EsQueryConfig } from '../../../../../src/plugins/data/common'; import { toElasticsearchQuery } from '../../../../../src/plugins/data/common/es_query'; import { KueryNode } from '../../../../../src/plugins/data/server'; diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts index 361ba5ff5e55de..f5455d1a630934 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { JsonValue } from '../../../../../../src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; type RenameAlertToRule = K extends `alertTypeId` ? `ruleTypeId` diff --git a/x-pack/plugins/graph/public/types/workspace_state.ts b/x-pack/plugins/graph/public/types/workspace_state.ts index 73d76cfd9cc572..e511a2eb5c7798 100644 --- a/x-pack/plugins/graph/public/types/workspace_state.ts +++ b/x-pack/plugins/graph/public/types/workspace_state.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { JsonObject } from '@kbn/common-utils'; import { FontawesomeIcon } from '../helpers/style_choices'; import { WorkspaceField, AdvancedSettings } from './app_state'; -import { JsonObject } from '../../../../../src/plugins/kibana_utils/public'; export interface WorkspaceNode { x: number; diff --git a/x-pack/plugins/infra/common/typed_json.ts b/x-pack/plugins/infra/common/typed_json.ts index 5b7bbdcfbc07bd..44409ab433a601 100644 --- a/x-pack/plugins/infra/common/typed_json.ts +++ b/x-pack/plugins/infra/common/typed_json.ts @@ -6,7 +6,7 @@ */ import * as rt from 'io-ts'; -import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana_utils/common'; +import { JsonArray, JsonObject, JsonValue } from '@kbn/common-utils'; export { JsonArray, JsonObject, JsonValue }; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx index 44d78591fbf2f3..0087d559a42e60 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx @@ -7,6 +7,7 @@ import React, { useMemo, useCallback, useEffect } from 'react'; import { noop } from 'lodash'; +import { JsonValue } from '@kbn/common-utils'; import { DataPublicPluginStart, esQuery, Filter } from '../../../../../../src/plugins/data/public'; import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; import { LogEntryCursor } from '../../../common/log_entry'; @@ -17,7 +18,6 @@ import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream'; import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration'; -import { JsonValue } from '../../../../../../src/plugins/kibana_utils/common'; import { Query } from '../../../../../../src/plugins/data/common'; import { LogStreamErrorBoundary } from './log_stream_error_boundary'; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx index 29e511b2467e10..9cffef270219e4 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx @@ -7,8 +7,8 @@ import stringify from 'json-stable-stringify'; import React from 'react'; +import { JsonArray, JsonValue } from '@kbn/common-utils'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting'; export const FieldValue: React.FC<{ diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx index 4fffa8eb0ee021..33e81756552d8e 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { LogColumn } from '../../../../common/log_entry'; import { isFieldColumn, isHighlightFieldColumn } from '../../../utils/log_entry'; diff --git a/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx b/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx index 3758e02e77312b..a6adc716e02fbc 100644 --- a/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx +++ b/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx @@ -6,7 +6,7 @@ */ import { ReactNode } from 'react'; -import { JsonValue } from '../../../../../src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; /** * Interface for common configuration properties, regardless of the column type. diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 1231a19f80ca25..1657d41d0b7936 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -8,6 +8,7 @@ import { GenericParams, SearchResponse } from 'elasticsearch'; import { Lifecycle } from '@hapi/hapi'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { JsonArray, JsonValue } from '@kbn/common-utils'; import { RouteConfig, RouteMethod } from '../../../../../../../src/core/server'; import { PluginSetup as DataPluginSetup, @@ -19,7 +20,6 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../pl import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server'; import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerting/server'; import { MlPluginSetup } from '../../../../../ml/server'; -import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; export interface InfraServerPluginSetupDeps { data: DataPluginSetup; diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index 3aaa747b945a82..9f2e9e2713bbc7 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -11,7 +11,7 @@ import { constant, identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import * as runtimeTypes from 'io-ts'; import { compact } from 'lodash'; -import { JsonArray } from '../../../../../../../src/plugins/kibana_utils/common'; +import { JsonArray } from '@kbn/common-utils'; import type { InfraPluginRequestHandlerContext } from '../../../types'; import { LogEntriesAdapter, diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index ad8650bbb0fb65..f8268570710f2c 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -6,7 +6,8 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; + import type { InfraPluginRequestHandlerContext } from '../../../types'; import { diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts index 0878131a69f0e6..33060f428b7ff3 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { InventoryItemType, MetricsUIAggregation, diff --git a/x-pack/plugins/infra/server/services/log_entries/message/message.ts b/x-pack/plugins/infra/server/services/log_entries/message/message.ts index 83d9aa6c8954ca..2deee584f5187a 100644 --- a/x-pack/plugins/infra/server/services/log_entries/message/message.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/message.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { JsonArray, JsonValue } from '@kbn/common-utils'; import { LogMessagePart } from '../../../../common/log_entry'; -import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; import { LogMessageFormattingCondition, LogMessageFormattingFieldValueConditionValue, diff --git a/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts b/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts index 4760382fd9bdd0..56d1b38e7e3902 100644 --- a/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; export interface LogMessageFormattingRule { when: LogMessageFormattingCondition; diff --git a/x-pack/plugins/infra/server/utils/serialized_query.ts b/x-pack/plugins/infra/server/utils/serialized_query.ts index 4f813d4fb137f3..4169e123d85322 100644 --- a/x-pack/plugins/infra/server/utils/serialized_query.ts +++ b/x-pack/plugins/infra/server/utils/serialized_query.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; export const parseFilterQuery = ( filterQuery: string | null | undefined diff --git a/x-pack/plugins/infra/server/utils/typed_search_strategy.ts b/x-pack/plugins/infra/server/utils/typed_search_strategy.ts index 546fd90a2da501..2482694474b0ed 100644 --- a/x-pack/plugins/infra/server/utils/typed_search_strategy.ts +++ b/x-pack/plugins/infra/server/utils/typed_search_strategy.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; import stringify from 'json-stable-stringify'; -import { JsonValue } from '../../../../../src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; import { jsonValueRT } from '../../common/typed_json'; import { SearchStrategyError } from '../../common/search_strategies/common/errors'; import { ShardFailure } from './elasticsearch_runtime_types'; diff --git a/x-pack/plugins/ml/common/types/es_client.ts b/x-pack/plugins/ml/common/types/es_client.ts index 67adda6b24c18a..29a7a81aa56939 100644 --- a/x-pack/plugins/ml/common/types/es_client.ts +++ b/x-pack/plugins/ml/common/types/es_client.ts @@ -7,9 +7,9 @@ import { estypes } from '@elastic/elasticsearch'; +import { JsonObject } from '@kbn/common-utils'; import { buildEsQuery } from '../../../../../src/plugins/data/common/es_query/es_query'; import type { DslQuery } from '../../../../../src/plugins/data/common/es_query/kuery'; -import type { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; import { isPopulatedObject } from '../util/object_utils'; diff --git a/x-pack/plugins/osquery/common/typed_json.ts b/x-pack/plugins/osquery/common/typed_json.ts index fb24b1dc0db5e3..8ce6907beb80bd 100644 --- a/x-pack/plugins/osquery/common/typed_json.ts +++ b/x-pack/plugins/osquery/common/typed_json.ts @@ -7,7 +7,7 @@ import { DslQuery, Filter } from 'src/plugins/data/common'; -import { JsonObject } from '../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; export type ESQuery = | ESRangeQuery diff --git a/x-pack/plugins/security_solution/common/typed_json.ts b/x-pack/plugins/security_solution/common/typed_json.ts index fb24b1dc0db5e3..8ce6907beb80bd 100644 --- a/x-pack/plugins/security_solution/common/typed_json.ts +++ b/x-pack/plugins/security_solution/common/typed_json.ts @@ -7,7 +7,7 @@ import { DslQuery, Filter } from 'src/plugins/data/common'; -import { JsonObject } from '../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; export type ESQuery = | ESRangeQuery diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts index bd026f486471f1..a71524f9e02a86 100644 --- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts @@ -7,6 +7,7 @@ import { isEmpty, isString, flow } from 'lodash/fp'; +import { JsonObject } from '@kbn/common-utils'; import { EsQueryConfig, Query, @@ -15,7 +16,6 @@ import { esKuery, IIndexPattern, } from '../../../../../../../src/plugins/data/public'; -import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/public'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts index 28a220c6f048a6..70e74356188c76 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts @@ -6,10 +6,10 @@ */ import type { IScopedClusterClient } from 'kibana/server'; +import { JsonObject } from '@kbn/common-utils'; import { parseFilterQuery } from '../../../../utils/serialized_query'; import { SafeResolverEvent } from '../../../../../common/endpoint/types'; import { PaginationBuilder } from '../utils/pagination'; -import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common'; interface TimeRange { from: string; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts index bf9b3ce6aa8f3a..331f622951515e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts @@ -7,8 +7,8 @@ import type { ApiResponse, estypes } from '@elastic/elasticsearch'; import { IScopedClusterClient } from 'src/core/server'; +import { JsonObject, JsonValue } from '@kbn/common-utils'; import { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types'; -import { JsonObject, JsonValue } from '../../../../../../../../../src/plugins/kibana_utils/common'; import { NodeID, TimeRange, docValueFields, validIDs } from '../utils/index'; interface DescendantsParams { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts index f9780d1469756e..7de038ccc9ae45 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts @@ -6,8 +6,8 @@ */ import { IScopedClusterClient } from 'src/core/server'; +import { JsonObject, JsonValue } from '@kbn/common-utils'; import { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types'; -import { JsonObject, JsonValue } from '../../../../../../../../../src/plugins/kibana_utils/common'; import { NodeID, TimeRange, docValueFields, validIDs } from '../utils/index'; interface LifecycleParams { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts index 24c97ad88b26ae..f21259980d464f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts @@ -6,7 +6,7 @@ */ import { IScopedClusterClient } from 'src/core/server'; -import { JsonObject } from '../../../../../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { EventStats, ResolverSchema } from '../../../../../../common/endpoint/types'; import { NodeID, TimeRange } from '../utils/index'; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts index befd69bdcf953c..24fc447173ba6c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { JsonObject } from '@kbn/common-utils'; import { SafeResolverEvent } from '../../../../../common/endpoint/types'; import { eventIDSafeVersion, timestampSafeVersion, } from '../../../../../common/endpoint/models/event'; -import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common'; type SearchAfterFields = [number, string]; diff --git a/x-pack/plugins/security_solution/server/endpoint/types.ts b/x-pack/plugins/security_solution/server/endpoint/types.ts index b3c7e58afe9915..6076aa9af635bf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/types.ts @@ -8,9 +8,9 @@ import { LoggerFactory } from 'kibana/server'; import { SearchResponse } from '@elastic/elasticsearch/api/types'; +import { JsonObject } from '@kbn/common-utils'; import { ConfigType } from '../config'; import { EndpointAppContextService } from './endpoint_app_context_services'; -import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; import { HostMetadata, MetadataQueryStrategyVersions } from '../../common/endpoint/types'; import { ExperimentalFeatures } from '../../common/experimental_features'; diff --git a/x-pack/plugins/security_solution/server/utils/serialized_query.ts b/x-pack/plugins/security_solution/server/utils/serialized_query.ts index fb5009eefa3180..7f8603ccab4b76 100644 --- a/x-pack/plugins/security_solution/server/utils/serialized_query.ts +++ b/x-pack/plugins/security_solution/server/utils/serialized_query.ts @@ -7,7 +7,7 @@ import { isEmpty, isPlainObject, isString } from 'lodash/fp'; -import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; export const parseFilterQuery = (filterQuery: string): JsonObject => { try { diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index 35eb0dfca7a6bc..073112f94e049b 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -7,7 +7,7 @@ import { mapValues } from 'lodash'; import stats from 'stats-lite'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { RawMonitoringStats, RawMonitoredStat, HealthStatus } from './monitoring_stats_stream'; import { AveragedStat } from './task_run_calcultors'; import { TaskPersistenceTypes } from './task_run_statistics'; diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts index 7e13e25457ed68..fdf60fe6dda2c3 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts @@ -9,7 +9,7 @@ import { TaskManagerConfig } from '../config'; import { of, Subject } from 'rxjs'; import { take, bufferCount } from 'rxjs/operators'; import { createMonitoringStatsStream, AggregatedStat } from './monitoring_stats_stream'; -import { JsonValue } from 'src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; beforeEach(() => { jest.resetAllMocks(); diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index 8338bf3197162e..78511f5a94ca07 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -9,7 +9,7 @@ import { merge, of, Observable } from 'rxjs'; import { map, scan } from 'rxjs/operators'; import { set } from '@elastic/safer-lodash-set'; import { Logger } from 'src/core/server'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { TaskStore } from '../task_store'; import { TaskPollingLifecycle } from '../polling_lifecycle'; import { diff --git a/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts b/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts index 0a6db350a88b9a..799ea054596c0a 100644 --- a/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts +++ b/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts @@ -6,7 +6,7 @@ */ import { Observable } from 'rxjs'; -import { JsonValue } from 'src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; export interface AggregatedStat { key: string; diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts index 4e2e689b71c88d..b0611437d87bec 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts @@ -6,7 +6,7 @@ */ import stats from 'stats-lite'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { isUndefined, countBy, mapValues } from 'lodash'; export interface AveragedStat extends JsonObject { diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts index eb6cb0796c33cc..b792f4ca475f93 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts @@ -7,7 +7,7 @@ import { combineLatest, Observable } from 'rxjs'; import { filter, startWith, map } from 'rxjs/operators'; -import { JsonObject, JsonValue } from 'src/plugins/kibana_utils/common'; +import { JsonObject, JsonValue } from '@kbn/common-utils'; import { isNumber, mapValues } from 'lodash'; import { AggregatedStatProvider, AggregatedStat } from './runtime_statistics_aggregator'; import { TaskLifecycleEvent } from '../polling_lifecycle'; diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 669f6198325485..abd86be522f0cd 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -8,7 +8,7 @@ import { combineLatest, Observable, timer } from 'rxjs'; import { mergeMap, map, filter, switchMap, catchError } from 'rxjs/operators'; import { Logger } from 'src/core/server'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { keyBy, mapValues } from 'lodash'; import { estypes } from '@elastic/elasticsearch'; import { AggregatedStatProvider } from './runtime_statistics_aggregator'; diff --git a/x-pack/plugins/task_manager/server/routes/health.ts b/x-pack/plugins/task_manager/server/routes/health.ts index cc2f6c6630e56d..0f43575d844816 100644 --- a/x-pack/plugins/task_manager/server/routes/health.ts +++ b/x-pack/plugins/task_manager/server/routes/health.ts @@ -16,7 +16,7 @@ import { Observable, Subject } from 'rxjs'; import { tap, map } from 'rxjs/operators'; import { throttleTime } from 'rxjs/operators'; import { isString } from 'lodash'; -import { JsonValue } from 'src/plugins/kibana_utils/common'; +import { JsonValue } from '@kbn/common-utils'; import { Logger, ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server'; import { MonitoringStats, diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 6a69921a36671e..c5a6ef877c47a4 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -8,10 +8,10 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import Mustache from 'mustache'; +import { JsonObject } from '@kbn/common-utils'; import { ActionGroupIdsOf } from '../../../../alerting/common'; import { UptimeAlertTypeFactory } from './types'; import { esKuery } from '../../../../../../src/plugins/data/server'; -import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common'; import { StatusCheckFilters, Ping, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts index c126de27158cc1..07047bd0be7bc5 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { JsonObject } from 'src/plugins/kibana_utils/public'; +import { JsonObject } from '@kbn/common-utils'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters'; diff --git a/x-pack/test/apm_api_integration/tests/services/annotations.ts b/x-pack/test/apm_api_integration/tests/services/annotations.ts index 9a634c9bf82470..34eadbe3c609ce 100644 --- a/x-pack/test/apm_api_integration/tests/services/annotations.ts +++ b/x-pack/test/apm_api_integration/tests/services/annotations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { merge, cloneDeep, isPlainObject } from 'lodash'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; diff --git a/x-pack/test/observability_api_integration/basic/tests/annotations.ts b/x-pack/test/observability_api_integration/basic/tests/annotations.ts index 05bfba42dd59ca..4a2c7b68f612e9 100644 --- a/x-pack/test/observability_api_integration/basic/tests/annotations.ts +++ b/x-pack/test/observability_api_integration/basic/tests/annotations.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/observability_api_integration/trial/tests/annotations.ts b/x-pack/test/observability_api_integration/trial/tests/annotations.ts index 1ea3460060bc9f..b1ef717ddfd88b 100644 --- a/x-pack/test/observability_api_integration/trial/tests/annotations.ts +++ b/x-pack/test/observability_api_integration/trial/tests/annotations.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { Annotation } from '../../../../plugins/observability/common/annotations'; import { FtrProviderContext } from '../../common/ftr_provider_context'; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts index 073bc44e89e61f..b3aeb55eb38a12 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { JsonObject } from '@kbn/common-utils'; import { eventsIndexPattern } from '../../../../plugins/security_solution/common/endpoint/constants'; import { eventIDSafeVersion, diff --git a/yarn.lock b/yarn.lock index a9a81585000b5e..4316e5f638c379 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2620,6 +2620,10 @@ version "0.0.0" uid "" +"@kbn/common-utils@link:bazel-bin/packages/kbn-common-utils": + version "0.0.0" + uid "" + "@kbn/config-schema@link:bazel-bin/packages/kbn-config-schema": version "0.0.0" uid "" From c5e74d8241d645ccaf266bcb7d9c909a9c95b6df Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 16 Jun 2021 11:35:07 -0400 Subject: [PATCH 04/15] [RAC][Security Solution] Pull Gap Remediation out of search_after_bulk_create (#102104) * Modify threshold rules to receive a single date range tuple * Modify threat match rules to receive a single date range tuple * Modify custom query rules to receive a single date range tuple * Fix up tests (partially) * Change log message to indicate single tuple instead of array * Bad test? * Prevent max_signals from being exceeded on threat match rule executions * Revert "Prevent max_signals from being exceeded on threat match rule executions" This reverts commit ba3b2f7a382ef7c369f02c7939e1495f72d92bfe. * Modify EQL rules to use date range tuple * Modify ML rules to use date range tuple * Fix ML/EQL tests * Use dateMath to parse moments in ML/Threshold tests * Add mocks for threshold test * Use dateMath for eql tests --- .../signals/executors/eql.test.ts | 10 +- .../detection_engine/signals/executors/eql.ts | 7 +- .../signals/executors/ml.test.ts | 12 +- .../detection_engine/signals/executors/ml.ts | 8 +- .../signals/executors/query.ts | 6 +- .../signals/executors/threat_match.ts | 6 +- .../signals/executors/threshold.test.ts | 25 +- .../signals/executors/threshold.ts | 160 ++++++------ .../signals/search_after_bulk_create.test.ts | 37 ++- .../signals/search_after_bulk_create.ts | 245 +++++++++--------- .../signals/signal_rule_alert_type.ts | 132 +++++----- .../threat_mapping/create_threat_signal.ts | 4 +- .../threat_mapping/create_threat_signals.ts | 4 +- .../signals/threat_mapping/types.ts | 4 +- .../lib/detection_engine/signals/types.ts | 4 +- 15 files changed, 352 insertions(+), 312 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index 947e7d573173ea..e7af3d484dfbd2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import dateMath from '@elastic/datemath'; import { loggingSystemMock } from 'src/core/server/mocks'; import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; import { eqlExecutor } from './eql'; @@ -23,6 +24,7 @@ describe('eql_executor', () => { let logger: ReturnType; let alertServices: AlertServicesMock; (getIndexVersion as jest.Mock).mockReturnValue(SIGNALS_TEMPLATE_VERSION); + const params = getEqlRuleParams(); const eqlSO = { id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', type: 'alert', @@ -40,10 +42,15 @@ describe('eql_executor', () => { interval: '5m', }, throttle: 'no_actions', - params: getEqlRuleParams(), + params, }, references: [], }; + const tuple = { + from: dateMath.parse(params.from)!, + to: dateMath.parse(params.to)!, + maxSignals: params.maxSignals, + }; const searchAfterSize = 7; beforeEach(() => { @@ -64,6 +71,7 @@ describe('eql_executor', () => { const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })]; const response = await eqlExecutor({ rule: eqlSO, + tuple, exceptionItems, services: alertServices, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index 28d1f3e19baeed..a187b730696829 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -28,6 +28,7 @@ import { AlertAttributes, BulkCreate, EqlSignalSearchResponse, + RuleRangeTuple, SearchAfterAndBulkCreateReturnType, WrappedSignalHit, } from '../types'; @@ -35,6 +36,7 @@ import { createSearchAfterReturnType, makeFloatString, wrapSignal } from '../uti export const eqlExecutor = async ({ rule, + tuple, exceptionItems, services, version, @@ -43,6 +45,7 @@ export const eqlExecutor = async ({ bulkCreate, }: { rule: SavedObject>; + tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; services: AlertServices; version: string; @@ -81,8 +84,8 @@ export const eqlExecutor = async ({ const request = buildEqlSearchRequest( ruleParams.query, inputIndex, - ruleParams.from, - ruleParams.to, + tuple.from.toISOString(), + tuple.to.toISOString(), searchAfterSize, ruleParams.timestampOverride, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts index 25a9d2c3f510fe..89c1392cb67ba7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import dateMath from '@elastic/datemath'; import { loggingSystemMock } from 'src/core/server/mocks'; import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; import { mlExecutor } from './ml'; @@ -26,7 +27,13 @@ describe('ml_executor', () => { const exceptionItems = [getExceptionListItemSchemaMock()]; let logger: ReturnType; let alertServices: AlertServicesMock; - const mlSO = sampleRuleSO(getMlRuleParams()); + const params = getMlRuleParams(); + const mlSO = sampleRuleSO(params); + const tuple = { + from: dateMath.parse(params.from)!, + to: dateMath.parse(params.to)!, + maxSignals: params.maxSignals, + }; const buildRuleMessage = buildRuleMessageFactory({ id: mlSO.id, ruleId: mlSO.attributes.params.ruleId, @@ -60,6 +67,7 @@ describe('ml_executor', () => { await expect( mlExecutor({ rule: mlSO, + tuple, ml: undefined, exceptionItems, services: alertServices, @@ -76,6 +84,7 @@ describe('ml_executor', () => { jobsSummaryMock.mockResolvedValue([]); const response = await mlExecutor({ rule: mlSO, + tuple, ml: mlMock, exceptionItems, services: alertServices, @@ -101,6 +110,7 @@ describe('ml_executor', () => { const response = await mlExecutor({ rule: mlSO, + tuple, ml: mlMock, exceptionItems, services: alertServices, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts index f5c7d8822b51f0..20c4cb16dadc8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts @@ -21,11 +21,12 @@ import { bulkCreateMlSignals } from '../bulk_create_ml_signals'; import { filterEventsAgainstList } from '../filters/filter_events_against_list'; import { findMlSignals } from '../find_ml_signals'; import { BuildRuleMessage } from '../rule_messages'; -import { AlertAttributes, BulkCreate, WrapHits } from '../types'; +import { AlertAttributes, BulkCreate, RuleRangeTuple, WrapHits } from '../types'; import { createErrorsFromShard, createSearchAfterReturnType, mergeReturns } from '../utils'; export const mlExecutor = async ({ rule, + tuple, ml, listClient, exceptionItems, @@ -36,6 +37,7 @@ export const mlExecutor = async ({ wrapHits, }: { rule: SavedObject>; + tuple: RuleRangeTuple; ml: SetupPlugins['ml']; listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; @@ -88,8 +90,8 @@ export const mlExecutor = async ({ savedObjectsClient: services.savedObjectsClient, jobIds: ruleParams.machineLearningJobId, anomalyThreshold: ruleParams.anomalyThreshold, - from: ruleParams.from, - to: ruleParams.to, + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), exceptionItems, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts index 9d76a06afa2755..385c01c2f1cda1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts @@ -24,7 +24,7 @@ import { QueryRuleParams, SavedQueryRuleParams } from '../../schemas/rule_schema export const queryExecutor = async ({ rule, - tuples, + tuple, listClient, exceptionItems, services, @@ -37,7 +37,7 @@ export const queryExecutor = async ({ wrapHits, }: { rule: SavedObject>; - tuples: RuleRangeTuple[]; + tuple: RuleRangeTuple; listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; services: AlertServices; @@ -63,7 +63,7 @@ export const queryExecutor = async ({ }); return searchAfterAndBulkCreate({ - tuples, + tuple, listClient, exceptionsList: exceptionItems, ruleSO: rule, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts index 078eb8362069cf..d0e22f696b222e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts @@ -23,7 +23,7 @@ import { ThreatRuleParams } from '../../schemas/rule_schemas'; export const threatMatchExecutor = async ({ rule, - tuples, + tuple, listClient, exceptionItems, services, @@ -36,7 +36,7 @@ export const threatMatchExecutor = async ({ wrapHits, }: { rule: SavedObject>; - tuples: RuleRangeTuple[]; + tuple: RuleRangeTuple; listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; services: AlertServices; @@ -51,7 +51,7 @@ export const threatMatchExecutor = async ({ const ruleParams = rule.attributes.params; const inputIndex = await getInputIndex(services, version, ruleParams.index); return createThreatSignals({ - tuples, + tuple, threatMapping: ruleParams.threatMapping, query: ruleParams.query, inputIndex, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts index f03e8b8a147aea..3906c669222386 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts @@ -5,18 +5,23 @@ * 2.0. */ +import dateMath from '@elastic/datemath'; import { loggingSystemMock } from 'src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; import { thresholdExecutor } from './threshold'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntryListMock } from '../../../../../../lists/common/schemas/types/entry_list.mock'; import { getThresholdRuleParams } from '../../schemas/rule_schemas.mock'; import { buildRuleMessageFactory } from '../rule_messages'; +import { sampleEmptyDocSearchResults } from '../__mocks__/es_results'; describe('threshold_executor', () => { const version = '8.0.0'; let logger: ReturnType; let alertServices: AlertServicesMock; + const params = getThresholdRuleParams(); const thresholdSO = { id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', type: 'alert', @@ -34,10 +39,15 @@ describe('threshold_executor', () => { interval: '5m', }, throttle: 'no_actions', - params: getThresholdRuleParams(), + params, }, references: [], }; + const tuple = { + from: dateMath.parse(params.from)!, + to: dateMath.parse(params.to)!, + maxSignals: params.maxSignals, + }; const buildRuleMessage = buildRuleMessageFactory({ id: thresholdSO.id, ruleId: thresholdSO.attributes.params.ruleId, @@ -47,6 +57,9 @@ describe('threshold_executor', () => { beforeEach(() => { alertServices = alertsMock.createAlertServices(); + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValue( + elasticsearchClientMock.createSuccessTransportRequestPromise(sampleEmptyDocSearchResults()) + ); logger = loggingSystemMock.createLogger(); }); @@ -55,14 +68,20 @@ describe('threshold_executor', () => { const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })]; const response = await thresholdExecutor({ rule: thresholdSO, - tuples: [], + tuple, exceptionItems, services: alertServices, version, logger, buildRuleMessage, startedAt: new Date(), - bulkCreate: jest.fn(), + bulkCreate: jest.fn().mockImplementation((hits) => ({ + errors: [], + success: true, + bulkCreateDuration: '0', + createdItemsCount: 0, + createdItems: [], + })), wrapHits: jest.fn(), }); expect(response.warningMessages.length).toEqual(1); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index 5e23128c9c148a..378d68fc13d2a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -39,7 +39,7 @@ import { BuildRuleMessage } from '../rule_messages'; export const thresholdExecutor = async ({ rule, - tuples, + tuple, exceptionItems, services, version, @@ -50,7 +50,7 @@ export const thresholdExecutor = async ({ wrapHits, }: { rule: SavedObject>; - tuples: RuleRangeTuple[]; + tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; services: AlertServices; version: string; @@ -70,90 +70,88 @@ export const thresholdExecutor = async ({ } const inputIndex = await getInputIndex(services, version, ruleParams.index); - for (const tuple of tuples) { - const { - thresholdSignalHistory, - searchErrors: previousSearchErrors, - } = await getThresholdSignalHistory({ - indexPattern: [ruleParams.outputIndex], - from: tuple.from.toISOString(), - to: tuple.to.toISOString(), - services, - logger, - ruleId: ruleParams.ruleId, - bucketByFields: ruleParams.threshold.field, - timestampOverride: ruleParams.timestampOverride, - buildRuleMessage, - }); + const { + thresholdSignalHistory, + searchErrors: previousSearchErrors, + } = await getThresholdSignalHistory({ + indexPattern: [ruleParams.outputIndex], + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + logger, + ruleId: ruleParams.ruleId, + bucketByFields: ruleParams.threshold.field, + timestampOverride: ruleParams.timestampOverride, + buildRuleMessage, + }); - const bucketFilters = await getThresholdBucketFilters({ - thresholdSignalHistory, - timestampOverride: ruleParams.timestampOverride, - }); + const bucketFilters = await getThresholdBucketFilters({ + thresholdSignalHistory, + timestampOverride: ruleParams.timestampOverride, + }); + + const esFilter = await getFilter({ + type: ruleParams.type, + filters: ruleParams.filters ? ruleParams.filters.concat(bucketFilters) : bucketFilters, + language: ruleParams.language, + query: ruleParams.query, + savedId: ruleParams.savedId, + services, + index: inputIndex, + lists: exceptionItems, + }); + + const { + searchResult: thresholdResults, + searchErrors, + searchDuration: thresholdSearchDuration, + } = await findThresholdSignals({ + inputIndexPattern: inputIndex, + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + logger, + filter: esFilter, + threshold: ruleParams.threshold, + timestampOverride: ruleParams.timestampOverride, + buildRuleMessage, + }); - const esFilter = await getFilter({ - type: ruleParams.type, - filters: ruleParams.filters ? ruleParams.filters.concat(bucketFilters) : bucketFilters, - language: ruleParams.language, - query: ruleParams.query, - savedId: ruleParams.savedId, - services, - index: inputIndex, - lists: exceptionItems, - }); + const { + success, + bulkCreateDuration, + createdItemsCount, + createdItems, + errors, + } = await bulkCreateThresholdSignals({ + someResult: thresholdResults, + ruleSO: rule, + filter: esFilter, + services, + logger, + inputIndexPattern: inputIndex, + signalsIndex: ruleParams.outputIndex, + startedAt, + from: tuple.from.toDate(), + thresholdSignalHistory, + bulkCreate, + wrapHits, + }); - const { + result = mergeReturns([ + result, + createSearchAfterReturnTypeFromResponse({ searchResult: thresholdResults, - searchErrors, - searchDuration: thresholdSearchDuration, - } = await findThresholdSignals({ - inputIndexPattern: inputIndex, - from: tuple.from.toISOString(), - to: tuple.to.toISOString(), - services, - logger, - filter: esFilter, - threshold: ruleParams.threshold, timestampOverride: ruleParams.timestampOverride, - buildRuleMessage, - }); - - const { + }), + createSearchAfterReturnType({ success, - bulkCreateDuration, - createdItemsCount, - createdItems, - errors, - } = await bulkCreateThresholdSignals({ - someResult: thresholdResults, - ruleSO: rule, - filter: esFilter, - services, - logger, - inputIndexPattern: inputIndex, - signalsIndex: ruleParams.outputIndex, - startedAt, - from: tuple.from.toDate(), - thresholdSignalHistory, - bulkCreate, - wrapHits, - }); - - result = mergeReturns([ - result, - createSearchAfterReturnTypeFromResponse({ - searchResult: thresholdResults, - timestampOverride: ruleParams.timestampOverride, - }), - createSearchAfterReturnType({ - success, - errors: [...errors, ...previousSearchErrors, ...searchErrors], - createdSignalsCount: createdItemsCount, - createdSignals: createdItems, - bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [], - searchAfterTimes: [thresholdSearchDuration], - }), - ]); - } + errors: [...errors, ...previousSearchErrors, ...searchErrors], + createdSignalsCount: createdItemsCount, + createdSignals: createdItems, + bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [], + searchAfterTimes: [thresholdSearchDuration], + }), + ]); return result; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index e4eb7e854f670f..184b49c2d6c7b9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -44,14 +44,14 @@ describe('searchAfterAndBulkCreate', () => { const sampleParams = getQueryRuleParams(); const ruleSO = sampleRuleSO(getQueryRuleParams()); sampleParams.maxSignals = 30; - let tuples: RuleRangeTuple[]; + let tuple: RuleRangeTuple; beforeEach(() => { jest.clearAllMocks(); listClient = listMock.getListClient(); listClient.searchListItemByValues = jest.fn().mockResolvedValue([]); inputIndexPattern = ['auditbeat-*']; mockService = alertsMock.createAlertServices(); - ({ tuples } = getRuleRangeTuples({ + tuple = getRuleRangeTuples({ logger: mockLogger, previousStartedAt: new Date(), from: sampleParams.from, @@ -59,7 +59,7 @@ describe('searchAfterAndBulkCreate', () => { interval: '5m', maxSignals: sampleParams.maxSignals, buildRuleMessage, - })); + }).tuples[0]; bulkCreate = bulkCreateFactory( mockLogger, mockService.scopedClusterClient.asCurrentUser, @@ -174,7 +174,7 @@ describe('searchAfterAndBulkCreate', () => { ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - tuples, + tuple, ruleSO, listClient, exceptionsList: [exceptionItem], @@ -279,7 +279,7 @@ describe('searchAfterAndBulkCreate', () => { ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [exceptionItem], services: mockService, @@ -357,7 +357,7 @@ describe('searchAfterAndBulkCreate', () => { ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [exceptionItem], services: mockService, @@ -416,7 +416,7 @@ describe('searchAfterAndBulkCreate', () => { ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [exceptionItem], services: mockService, @@ -495,7 +495,7 @@ describe('searchAfterAndBulkCreate', () => { const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [], services: mockService, @@ -550,7 +550,7 @@ describe('searchAfterAndBulkCreate', () => { ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [exceptionItem], services: mockService, @@ -569,11 +569,6 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(1); expect(createdSignalsCount).toEqual(0); // should not create any signals because all events were in the allowlist expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); - // I don't like testing log statements since logs change but this is the best - // way I can think of to ensure this section is getting hit with this test case. - expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[7][0]).toContain( - 'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"' - ); }); test('should return success when no sortId present but search results are in the allowlist', async () => { @@ -627,7 +622,7 @@ describe('searchAfterAndBulkCreate', () => { ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [exceptionItem], services: mockService, @@ -701,7 +696,7 @@ describe('searchAfterAndBulkCreate', () => { ); const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [], services: mockService, @@ -746,7 +741,7 @@ describe('searchAfterAndBulkCreate', () => { const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ listClient, exceptionsList: [exceptionItem], - tuples, + tuple, ruleSO, services: mockService, logger: mockLogger, @@ -793,7 +788,7 @@ describe('searchAfterAndBulkCreate', () => { const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ listClient, exceptionsList: [exceptionItem], - tuples, + tuple, ruleSO, services: mockService, logger: mockLogger, @@ -854,7 +849,7 @@ describe('searchAfterAndBulkCreate', () => { const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ listClient, exceptionsList: [exceptionItem], - tuples, + tuple, ruleSO, services: mockService, logger: mockLogger, @@ -979,7 +974,7 @@ describe('searchAfterAndBulkCreate', () => { errors, } = await searchAfterAndBulkCreate({ ruleSO, - tuples, + tuple, listClient, exceptionsList: [], services: mockService, @@ -1075,7 +1070,7 @@ describe('searchAfterAndBulkCreate', () => { const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ enrichment: mockEnrichment, ruleSO, - tuples, + tuple, listClient, exceptionsList: [], services: mockService, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index bb2e57b0606e59..eb4af0c38ce254 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -23,7 +23,7 @@ import { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType } fr // search_after through documents and re-index using bulk endpoint. export const searchAfterAndBulkCreate = async ({ - tuples: totalToFromTuples, + tuple, ruleSO, exceptionsList, services, @@ -49,150 +49,143 @@ export const searchAfterAndBulkCreate = async ({ // to ensure we don't exceed maxSignals let signalsCreatedCount = 0; - const tuplesToBeLogged = [...totalToFromTuples]; - logger.debug(buildRuleMessage(`totalToFromTuples: ${totalToFromTuples.length}`)); - - while (totalToFromTuples.length > 0) { - const tuple = totalToFromTuples.pop(); - if (tuple == null || tuple.to == null || tuple.from == null) { - logger.error(buildRuleMessage(`[-] malformed date tuple`)); - return createSearchAfterReturnType({ - success: false, - errors: ['malformed date tuple'], - }); - } - signalsCreatedCount = 0; - while (signalsCreatedCount < tuple.maxSignals) { - try { - let mergedSearchResults = createSearchResultReturnType(); - logger.debug(buildRuleMessage(`sortIds: ${sortIds}`)); + if (tuple == null || tuple.to == null || tuple.from == null) { + logger.error(buildRuleMessage(`[-] malformed date tuple`)); + return createSearchAfterReturnType({ + success: false, + errors: ['malformed date tuple'], + }); + } + signalsCreatedCount = 0; + while (signalsCreatedCount < tuple.maxSignals) { + try { + let mergedSearchResults = createSearchResultReturnType(); + logger.debug(buildRuleMessage(`sortIds: ${sortIds}`)); - if (hasSortId) { - const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ - buildRuleMessage, - searchAfterSortIds: sortIds, - index: inputIndexPattern, - from: tuple.from.toISOString(), - to: tuple.to.toISOString(), - services, - logger, - // @ts-expect-error please, declare a type explicitly instead of unknown - filter, - pageSize: Math.ceil(Math.min(tuple.maxSignals, pageSize)), + if (hasSortId) { + const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ + buildRuleMessage, + searchAfterSortIds: sortIds, + index: inputIndexPattern, + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + logger, + // @ts-expect-error please, declare a type explicitly instead of unknown + filter, + pageSize: Math.ceil(Math.min(tuple.maxSignals, pageSize)), + timestampOverride: ruleParams.timestampOverride, + }); + mergedSearchResults = mergeSearchResults([mergedSearchResults, searchResult]); + toReturn = mergeReturns([ + toReturn, + createSearchAfterReturnTypeFromResponse({ + searchResult: mergedSearchResults, timestampOverride: ruleParams.timestampOverride, - }); - mergedSearchResults = mergeSearchResults([mergedSearchResults, searchResult]); - toReturn = mergeReturns([ - toReturn, - createSearchAfterReturnTypeFromResponse({ - searchResult: mergedSearchResults, - timestampOverride: ruleParams.timestampOverride, - }), - createSearchAfterReturnType({ - searchAfterTimes: [searchDuration], - errors: searchErrors, - }), - ]); + }), + createSearchAfterReturnType({ + searchAfterTimes: [searchDuration], + errors: searchErrors, + }), + ]); - const lastSortIds = getSafeSortIds( - searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort - ); - if (lastSortIds != null && lastSortIds.length !== 0) { - sortIds = lastSortIds; - hasSortId = true; - } else { - hasSortId = false; - } + const lastSortIds = getSafeSortIds( + searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort + ); + if (lastSortIds != null && lastSortIds.length !== 0) { + sortIds = lastSortIds; + hasSortId = true; + } else { + hasSortId = false; } + } + + // determine if there are any candidate signals to be processed + const totalHits = createTotalHitsFromSearchResult({ searchResult: mergedSearchResults }); + logger.debug(buildRuleMessage(`totalHits: ${totalHits}`)); + logger.debug( + buildRuleMessage(`searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}`) + ); - // determine if there are any candidate signals to be processed - const totalHits = createTotalHitsFromSearchResult({ searchResult: mergedSearchResults }); - logger.debug(buildRuleMessage(`totalHits: ${totalHits}`)); + if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { logger.debug( - buildRuleMessage(`searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}`) + buildRuleMessage( + `${ + totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length' + } was 0, exiting early` + ) ); + break; + } - if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { - logger.debug( - buildRuleMessage( - `${ - totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length' - } was 0, exiting and moving on to next tuple` - ) - ); - break; - } - - // filter out the search results that match with the values found in the list. - // the resulting set are signals to be indexed, given they are not duplicates - // of signals already present in the signals index. - const filteredEvents = await filterEventsAgainstList({ - listClient, - exceptionsList, - logger, - eventSearchResult: mergedSearchResults, - buildRuleMessage, - }); - - // only bulk create if there are filteredEvents leftover - // if there isn't anything after going through the value list filter - // skip the call to bulk create and proceed to the next search_after, - // if there is a sort id to continue the search_after with. - if (filteredEvents.hits.hits.length !== 0) { - // make sure we are not going to create more signals than maxSignals allows - if (signalsCreatedCount + filteredEvents.hits.hits.length > tuple.maxSignals) { - filteredEvents.hits.hits = filteredEvents.hits.hits.slice( - 0, - tuple.maxSignals - signalsCreatedCount - ); - } - const enrichedEvents = await enrichment(filteredEvents); - const wrappedDocs = wrapHits(enrichedEvents.hits.hits); + // filter out the search results that match with the values found in the list. + // the resulting set are signals to be indexed, given they are not duplicates + // of signals already present in the signals index. + const filteredEvents = await filterEventsAgainstList({ + listClient, + exceptionsList, + logger, + eventSearchResult: mergedSearchResults, + buildRuleMessage, + }); - const { - bulkCreateDuration: bulkDuration, - createdItemsCount: createdCount, - createdItems, - success: bulkSuccess, - errors: bulkErrors, - } = await bulkCreate(wrappedDocs); - toReturn = mergeReturns([ - toReturn, - createSearchAfterReturnType({ - success: bulkSuccess, - createdSignalsCount: createdCount, - createdSignals: createdItems, - bulkCreateTimes: bulkDuration ? [bulkDuration] : undefined, - errors: bulkErrors, - }), - ]); - signalsCreatedCount += createdCount; - logger.debug(buildRuleMessage(`created ${createdCount} signals`)); - logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`)); - logger.debug( - buildRuleMessage(`enrichedEvents.hits.hits: ${enrichedEvents.hits.hits.length}`) + // only bulk create if there are filteredEvents leftover + // if there isn't anything after going through the value list filter + // skip the call to bulk create and proceed to the next search_after, + // if there is a sort id to continue the search_after with. + if (filteredEvents.hits.hits.length !== 0) { + // make sure we are not going to create more signals than maxSignals allows + if (signalsCreatedCount + filteredEvents.hits.hits.length > tuple.maxSignals) { + filteredEvents.hits.hits = filteredEvents.hits.hits.slice( + 0, + tuple.maxSignals - signalsCreatedCount ); - - sendAlertTelemetryEvents(logger, eventsTelemetry, enrichedEvents, buildRuleMessage); } + const enrichedEvents = await enrichment(filteredEvents); + const wrappedDocs = wrapHits(enrichedEvents.hits.hits); - if (!hasSortId) { - logger.debug(buildRuleMessage('ran out of sort ids to sort on')); - break; - } - } catch (exc: unknown) { - logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`)); - return mergeReturns([ + const { + bulkCreateDuration: bulkDuration, + createdItemsCount: createdCount, + createdItems, + success: bulkSuccess, + errors: bulkErrors, + } = await bulkCreate(wrappedDocs); + toReturn = mergeReturns([ toReturn, createSearchAfterReturnType({ - success: false, - errors: [`${exc}`], + success: bulkSuccess, + createdSignalsCount: createdCount, + createdSignals: createdItems, + bulkCreateTimes: bulkDuration ? [bulkDuration] : undefined, + errors: bulkErrors, }), ]); + signalsCreatedCount += createdCount; + logger.debug(buildRuleMessage(`created ${createdCount} signals`)); + logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`)); + logger.debug( + buildRuleMessage(`enrichedEvents.hits.hits: ${enrichedEvents.hits.hits.length}`) + ); + + sendAlertTelemetryEvents(logger, eventsTelemetry, enrichedEvents, buildRuleMessage); + } + + if (!hasSortId) { + logger.debug(buildRuleMessage('ran out of sort ids to sort on')); + break; } + } catch (exc: unknown) { + logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`)); + return mergeReturns([ + toReturn, + createSearchAfterReturnType({ + success: false, + errors: [`${exc}`], + }), + ]); } } logger.debug(buildRuleMessage(`[+] completed bulk index of ${toReturn.createdSignalsCount}`)); - toReturn.totalToFromTuples = tuplesToBeLogged; return toReturn; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 0a2e22bc44b60e..bb1e50c14d4014 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -235,74 +235,86 @@ export const signalRulesAlertType = ({ if (isMlRule(type)) { const mlRuleSO = asTypeSpecificSO(savedObject, machineLearningRuleParams); - result = await mlExecutor({ - rule: mlRuleSO, - ml, - listClient, - exceptionItems, - services, - logger, - buildRuleMessage, - bulkCreate, - wrapHits, - }); + for (const tuple of tuples) { + result = await mlExecutor({ + rule: mlRuleSO, + tuple, + ml, + listClient, + exceptionItems, + services, + logger, + buildRuleMessage, + bulkCreate, + wrapHits, + }); + } } else if (isThresholdRule(type)) { const thresholdRuleSO = asTypeSpecificSO(savedObject, thresholdRuleParams); - result = await thresholdExecutor({ - rule: thresholdRuleSO, - tuples, - exceptionItems, - services, - version, - logger, - buildRuleMessage, - startedAt, - bulkCreate, - wrapHits, - }); + for (const tuple of tuples) { + result = await thresholdExecutor({ + rule: thresholdRuleSO, + tuple, + exceptionItems, + services, + version, + logger, + buildRuleMessage, + startedAt, + bulkCreate, + wrapHits, + }); + } } else if (isThreatMatchRule(type)) { const threatRuleSO = asTypeSpecificSO(savedObject, threatRuleParams); - result = await threatMatchExecutor({ - rule: threatRuleSO, - tuples, - listClient, - exceptionItems, - services, - version, - searchAfterSize, - logger, - eventsTelemetry, - buildRuleMessage, - bulkCreate, - wrapHits, - }); + for (const tuple of tuples) { + result = await threatMatchExecutor({ + rule: threatRuleSO, + tuple, + listClient, + exceptionItems, + services, + version, + searchAfterSize, + logger, + eventsTelemetry, + buildRuleMessage, + bulkCreate, + wrapHits, + }); + } } else if (isQueryRule(type)) { const queryRuleSO = validateQueryRuleTypes(savedObject); - result = await queryExecutor({ - rule: queryRuleSO, - tuples, - listClient, - exceptionItems, - services, - version, - searchAfterSize, - logger, - eventsTelemetry, - buildRuleMessage, - bulkCreate, - wrapHits, - }); + for (const tuple of tuples) { + result = await queryExecutor({ + rule: queryRuleSO, + tuple, + listClient, + exceptionItems, + services, + version, + searchAfterSize, + logger, + eventsTelemetry, + buildRuleMessage, + bulkCreate, + wrapHits, + }); + } } else if (isEqlRule(type)) { const eqlRuleSO = asTypeSpecificSO(savedObject, eqlRuleParams); - result = await eqlExecutor({ - rule: eqlRuleSO, - exceptionItems, - services, - version, - searchAfterSize, - bulkCreate, - logger, - }); + for (const tuple of tuples) { + result = await eqlExecutor({ + rule: eqlRuleSO, + tuple, + exceptionItems, + services, + version, + searchAfterSize, + bulkCreate, + logger, + }); + } } else { throw new Error(`unknown rule type ${type}`); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts index 3e30a08f1ae69c..806f5e47608e40 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts @@ -13,7 +13,7 @@ import { CreateThreatSignalOptions } from './types'; import { SearchAfterAndBulkCreateReturnType } from '../types'; export const createThreatSignal = async ({ - tuples, + tuple, threatMapping, threatEnrichment, query, @@ -70,7 +70,7 @@ export const createThreatSignal = async ({ ); const result = await searchAfterAndBulkCreate({ - tuples, + tuple, listClient, exceptionsList: exceptionItems, ruleSO, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index 5054ab1b2cca50..169a820392a6e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -15,7 +15,7 @@ import { combineConcurrentResults } from './utils'; import { buildThreatEnrichment } from './build_threat_enrichment'; export const createThreatSignals = async ({ - tuples, + tuple, threatMapping, query, inputIndex, @@ -104,7 +104,7 @@ export const createThreatSignals = async ({ const concurrentSearchesPerformed = chunks.map>( (slicedChunk) => createThreatSignal({ - tuples, + tuple, threatEnrichment, threatMapping, query, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index 34b064b0f88053..ded79fc647ac41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -40,7 +40,7 @@ import { ThreatRuleParams } from '../../schemas/rule_schemas'; export type SortOrderOrUndefined = 'asc' | 'desc' | undefined; export interface CreateThreatSignalsOptions { - tuples: RuleRangeTuple[]; + tuple: RuleRangeTuple; threatMapping: ThreatMapping; query: string; inputIndex: string[]; @@ -70,7 +70,7 @@ export interface CreateThreatSignalsOptions { } export interface CreateThreatSignalOptions { - tuples: RuleRangeTuple[]; + tuple: RuleRangeTuple; threatMapping: ThreatMapping; threatEnrichment: SignalsEnrichment; query: string; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index c35eb04ba12707..8a6ce91b2575ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -262,11 +262,11 @@ export type WrapHits = ( ) => Array>; export interface SearchAfterAndBulkCreateParams { - tuples: Array<{ + tuple: { to: moment.Moment; from: moment.Moment; maxSignals: number; - }>; + }; ruleSO: SavedObject; services: AlertServices; listClient: ListClient; From 973d0578461a8823eb529b38ee7a5edc427b93a0 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 16 Jun 2021 18:01:53 +0200 Subject: [PATCH 05/15] Upgrade normalize-url from v4.5.0 to v4.5.1 (#102291) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4316e5f638c379..353527731cb04e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20304,9 +20304,9 @@ normalize-url@^3.0.0: integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== now-and-later@^2.0.0: version "2.0.0" From d2e81ee785df812135faf775e14f7aaaf58eb901 Mon Sep 17 00:00:00 2001 From: John Dorlus Date: Wed, 16 Jun 2021 12:07:50 -0400 Subject: [PATCH 06/15] CIT for circle processor (renewed PR) (#102277) * Added CITs for Circle processor. * Fixed issue with form function using int instead of string. * Added changed per nits. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__jest__/processors/circle.test.tsx | 114 ++++++++++++++++++ .../__jest__/processors/processor.helpers.tsx | 2 + .../processor_form/processors/circle.tsx | 2 + 3 files changed, 118 insertions(+) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx new file mode 100644 index 00000000000000..e29bb2ac6e92ec --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue } from './processor.helpers'; + +const CIRCLE_TYPE = 'circle'; + +describe('Processor: Circle', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + testBed.component.update(); + const { + actions: { addProcessor, addProcessorType }, + } = testBed; + // Open the processor flyout + addProcessor(); + + // Add type (the other fields are not visible until a type is selected) + await addProcessorType(CIRCLE_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the type defined + await saveNewProcessor(); + + // Expect form error as "field" and "shape_type" are required parameters + expect(form.getErrorsMessages()).toEqual([ + 'A field value is required.', + 'A shape type value is required.', + ]); + }); + + test('saves with required parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + // Save the field + form.setSelectValue('shapeSelectorField', 'shape'); + // Set the error distance + form.setInputValue('errorDistanceField.input', '10'); + + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, CIRCLE_TYPE); + + expect(processors[0].circle).toEqual({ + field: 'field_1', + error_distance: 10, + shape_type: 'shape', + }); + }); + + test('allows optional parameters to be set', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + // Select the shape + form.setSelectValue('shapeSelectorField', 'geo_shape'); + // Add "target_field" value + form.setInputValue('targetField.input', 'target_field'); + + form.setInputValue('errorDistanceField.input', '10'); + + // Save the field with new changes + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, CIRCLE_TYPE); + expect(processors[0].circle).toEqual({ + field: 'field_1', + error_distance: 10, + shape_type: 'geo_shape', + target_field: 'target_field', + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index c00f09b2d2b06c..15e8c323b1308e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -151,6 +151,8 @@ type TestSubject = | 'keepOriginalField.input' | 'removeIfSuccessfulField.input' | 'targetFieldsField.input' + | 'shapeSelectorField' + | 'errorDistanceField.input' | 'separatorValueField.input' | 'quoteValueField.input' | 'emptyValueField.input' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx index acb480df6d35f0..74a7f37d841aee 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx @@ -97,6 +97,7 @@ export const Circle: FunctionComponent = () => { /> { Date: Wed, 16 Jun 2021 12:42:15 -0400 Subject: [PATCH 07/15] Fix 7.13 aggregation reference issue (#102256) --- docs/user/dashboard/aggregation-reference.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/dashboard/aggregation-reference.asciidoc b/docs/user/dashboard/aggregation-reference.asciidoc index 001114578a1cd0..cb5c484def3b9d 100644 --- a/docs/user/dashboard/aggregation-reference.asciidoc +++ b/docs/user/dashboard/aggregation-reference.asciidoc @@ -190,8 +190,8 @@ For information about {es} metrics aggregations, refer to {ref}/search-aggregati | Metrics with filters | -^| X | +^| X | | Average From dbe3ca97082b62247098ec59a78cb129f05250c4 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 16 Jun 2021 12:05:56 -0500 Subject: [PATCH 08/15] Add auto-backport by default to ech renovate bot prs (#102208) --- renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index f533eac4796508..2a3b9d740ee93b 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -39,7 +39,7 @@ packageNames: ['@elastic/charts'], reviewers: ['markov00', 'nickofthyme'], matchBaseBranches: ['master'], - labels: ['release_note:skip', 'v8.0.0', 'v7.14.0'], + labels: ['release_note:skip', 'v8.0.0', 'v7.14.0', 'auto-backport'], enabled: true, }, { From c8256d57bdf23de5354b178e418b8e6694851c64 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 16 Jun 2021 10:07:21 -0700 Subject: [PATCH 09/15] skip flaky suite (#101984) --- x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index 7d235d9e181082..bbd212b61e4394 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -11,7 +11,8 @@ import { delay } from 'bluebird'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { - describe('uptime alerts', () => { + // FLAKY: https://github.com/elastic/kibana/issues/101984 + describe.skip('uptime alerts', () => { const pageObjects = getPageObjects(['common', 'uptime']); const supertest = getService('supertest'); const retry = getService('retry'); From f4e0895b173faf6c0878b82182265697d79ab481 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 16 Jun 2021 10:10:38 -0700 Subject: [PATCH 10/15] skip flaky suite (#100296) --- .../security_solution_endpoint/apps/endpoint/policy_details.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 44348d1ad0d9c4..e01a24d2ea8d54 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -21,7 +21,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const policyTestResources = getService('policyTestResources'); - describe('When on the Endpoint Policy Details Page', function () { + // FLAKY: https://github.com/elastic/kibana/issues/100296 + describe.skip('When on the Endpoint Policy Details Page', function () { describe('with an invalid policy id', () => { it('should display an error', async () => { await pageObjects.policy.navigateToPolicyDetails('invalid-id'); From 3e723045a048ea692c3f1e01700096a3983291b1 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 16 Jun 2021 10:14:04 -0700 Subject: [PATCH 11/15] remove nested skip (#100296) --- .../security_solution_endpoint/apps/endpoint/policy_details.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index e01a24d2ea8d54..ae60935013d272 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -757,8 +757,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/100296 - describe.skip('when on Ingest Policy Edit Package Policy page', async () => { + describe('when on Ingest Policy Edit Package Policy page', async () => { let policyInfo: PolicyTestResourceInfo; beforeEach(async () => { // Create a policy and navigate to Ingest app From 4a941565502547f96bab72786e1ac11f61f19558 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Wed, 16 Jun 2021 13:29:38 -0400 Subject: [PATCH 12/15] [Fleet + Integrations UI] Migrate Fleet UI to new tabbed layout (#101828) * WIP: Migrate fleet to new page layout system * Add 'Add Agent' button to agents table * Fix flyout import in search and filter bar * Place settings/feedback in header * Move actions to top nav * Fix i18n + types + unit test failures * Remove unused props in DefaultLayout * Fix background height in Fleet layout This is fixed through a hack for now, because Kibana's layout doesn't allow apps to flex the top-level wrapper via `flex: 1`. The same behavior reported in the original issue (#101781) is present in all other Kibana apps. Fixes #101781 * Use euiHeaderHeightCompensation for min-height calc * Move settings portal to app component * Fix agent details URL in failing unit test * Remove unreferenced overview files + update functional tests * Remove unneeded fragment * Remove beta badges in Fleet + Integrations Fixes #100731 * Fix i18n * Fix page path reference * Fix failing tests * Re-fix i18n post merge Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/kibana.json | 10 +- .../fleet/public/applications/fleet/app.tsx | 129 +++++++---- .../fleet/hooks/use_breadcrumbs.tsx | 33 +-- .../fleet/public/applications/fleet/index.tsx | 7 +- .../applications/fleet/layouts/default.tsx | 207 +++++++----------- .../fleet/sections/agent_policy/index.tsx | 6 +- .../sections/agent_policy/list_page/index.tsx | 37 +--- .../agent_details_integrations.tsx | 2 +- .../agents/agent_details_page/index.tsx | 17 +- .../components/search_and_filter_bar.tsx | 24 +- .../sections/agents/agent_list_page/index.tsx | 6 +- .../agents/components/list_layout.tsx | 101 --------- .../enrollment_token_list_page/index.tsx | 7 +- .../fleet/sections/agents/index.tsx | 28 +-- .../fleet/sections/data_stream/index.tsx | 5 +- .../sections/data_stream/list_page/index.tsx | 207 ++++++++---------- .../applications/fleet/sections/index.tsx | 5 +- .../components/agent_policy_section.tsx | 78 ------- .../overview/components/agent_section.tsx | 87 -------- .../components/datastream_section.tsx | 99 --------- .../components/integration_section.tsx | 88 -------- .../overview/components/overview_panel.tsx | 74 ------- .../overview/components/overview_stats.tsx | 24 -- .../fleet/sections/overview/index.tsx | 110 ---------- .../integrations/layouts/default.tsx | 11 +- .../managed_instructions.tsx | 2 +- .../public/components/linked_agent_count.tsx | 2 +- .../fleet/public/constants/page_paths.ts | 38 ++-- .../fleet/public/layouts/without_header.tsx | 7 + .../fleet/public/mock/plugin_dependencies.ts | 2 + x-pack/plugins/fleet/public/plugin.ts | 3 + .../action_results/action_results_summary.tsx | 2 +- .../osquery/public/results/results_table.tsx | 2 +- .../view/hooks/use_endpoint_action_items.tsx | 8 +- .../pages/endpoint_hosts/view/index.test.tsx | 4 +- .../pages/endpoint_hosts/view/index.tsx | 4 +- .../endpoint/routes/actions/isolation.ts | 4 +- .../translations/translations/ja-JP.json | 38 ---- .../translations/translations/zh-CN.json | 38 ---- .../apps/fleet/agents_page.ts | 38 ++++ .../test/fleet_functional/apps/fleet/index.ts | 2 +- .../apps/fleet/overview_page.ts | 38 ---- .../{overview_page.ts => agents_page.ts} | 23 +- .../fleet_functional/page_objects/index.ts | 4 +- 44 files changed, 421 insertions(+), 1240 deletions(-) delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx create mode 100644 x-pack/test/fleet_functional/apps/fleet/agents_page.ts delete mode 100644 x-pack/test/fleet_functional/apps/fleet/overview_page.ts rename x-pack/test/fleet_functional/page_objects/{overview_page.ts => agents_page.ts} (55%) diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 4a4019e3e9e47f..ca1407be2008a1 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -4,14 +4,8 @@ "server": true, "ui": true, "configPath": ["xpack", "fleet"], - "requiredPlugins": ["licensing", "data", "encryptedSavedObjects"], - "optionalPlugins": [ - "security", - "features", - "cloud", - "usageCollection", - "home" - ], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation"], + "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils"] } diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 1398e121c68700..1072a6b66419eb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -7,7 +7,7 @@ import React, { memo, useEffect, useState } from 'react'; import type { AppMountParameters } from 'kibana/public'; -import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel } from '@elastic/eui'; +import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel, EuiPortal } from '@elastic/eui'; import type { History } from 'history'; import { createHashHistory } from 'history'; import { Router, Redirect, Route, Switch } from 'react-router-dom'; @@ -16,11 +16,13 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import useObservable from 'react-use/lib/useObservable'; +import type { TopNavMenuData } from 'src/plugins/navigation/public'; + import type { FleetConfigType, FleetStartServices } from '../../plugin'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; -import { PackageInstallProvider } from '../integrations/hooks'; +import { PackageInstallProvider, useUrlModal } from '../integrations/hooks'; import { ConfigContext, @@ -30,25 +32,25 @@ import { sendGetPermissionsCheck, sendSetup, useBreadcrumbs, - useConfig, useStartServices, UIExtensionsContext, } from './hooks'; -import { Error, Loading } from './components'; +import { Error, Loading, SettingFlyout } from './components'; import type { UIExtensionsStorage } from './types'; import { FLEET_ROUTING_PATHS } from './constants'; import { DefaultLayout, WithoutHeaderLayout } from './layouts'; import { AgentPolicyApp } from './sections/agent_policy'; import { DataStreamApp } from './sections/data_stream'; -import { FleetApp } from './sections/agents'; -import { IngestManagerOverview } from './sections/overview'; -import { ProtectedRoute } from './index'; +import { AgentsApp } from './sections/agents'; import { CreatePackagePolicyPage } from './sections/agent_policy/create_package_policy_page'; +import { EnrollmentTokenListPage } from './sections/agents/enrollment_token_list_page'; + +const FEEDBACK_URL = 'https://ela.st/fleet-feedback'; const ErrorLayout = ({ children }: { children: JSX.Element }) => ( - + {children} @@ -233,37 +235,82 @@ export const FleetAppContext: React.FC<{ } ); -export const AppRoutes = memo(() => { - const { agents } = useConfig(); +const FleetTopNav = memo( + ({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => { + const { getModalHref } = useUrlModal(); + const services = useStartServices(); - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}); + const { TopNavMenu } = services.navigation.ui; + + const topNavConfig: TopNavMenuData[] = [ + { + label: i18n.translate('xpack.fleet.appNavigation.sendFeedbackButton', { + defaultMessage: 'Send Feedback', + }), + iconType: 'popout', + run: () => window.open(FEEDBACK_URL), + }, + + { + label: i18n.translate('xpack.fleet.appNavigation.settingsButton', { + defaultMessage: 'Fleet settings', + }), + iconType: 'gear', + run: () => (window.location.href = getModalHref('settings')), + }, + ]; + return ( + + ); + } +); + +export const AppRoutes = memo( + ({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => { + const { modal, setModal } = useUrlModal(); + + return ( + <> + + + {modal === 'settings' && ( + + { + setModal(null); + }} + /> + + )} + + + + + + + + + + + + + + + + {/* TODO: Move this route to the Integrations app */} + + + + + + + + + + ); + } +); diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index fd980475dc9194..254885ea71b1e4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -20,7 +20,7 @@ interface AdditionalBreadcrumbOptions { type Breadcrumb = ChromeBreadcrumb & Partial; const BASE_BREADCRUMB: Breadcrumb = { - href: pagePathGetters.overview()[1], + href: pagePathGetters.base()[1], text: i18n.translate('xpack.fleet.breadcrumbs.appTitle', { defaultMessage: 'Fleet', }), @@ -38,15 +38,6 @@ const breadcrumbGetters: { [key in Page]?: (values: DynamicPagePathValues) => Breadcrumb[]; } = { base: () => [BASE_BREADCRUMB], - overview: () => [ - BASE_BREADCRUMB, - { - text: i18n.translate('xpack.fleet.breadcrumbs.overviewPageTitle', { - defaultMessage: 'Overview', - }), - }, - ], - policies: () => [ BASE_BREADCRUMB, { @@ -122,15 +113,7 @@ const breadcrumbGetters: { }), }, ], - fleet: () => [ - BASE_BREADCRUMB, - { - text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', { - defaultMessage: 'Agents', - }), - }, - ], - fleet_agent_list: () => [ + agent_list: () => [ BASE_BREADCRUMB, { text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', { @@ -138,24 +121,18 @@ const breadcrumbGetters: { }), }, ], - fleet_agent_details: ({ agentHost }) => [ + agent_details: ({ agentHost }) => [ BASE_BREADCRUMB, { - href: pagePathGetters.fleet()[1], + href: pagePathGetters.agent_list({})[1], text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', { defaultMessage: 'Agents', }), }, { text: agentHost }, ], - fleet_enrollment_tokens: () => [ + enrollment_tokens: () => [ BASE_BREADCRUMB, - { - href: pagePathGetters.fleet()[1], - text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', { - defaultMessage: 'Agents', - }), - }, { text: i18n.translate('xpack.fleet.breadcrumbs.enrollmentTokensPageTitle', { defaultMessage: 'Enrollment tokens', diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index 7d31fb31b36a43..8942c13a0a69db 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -37,6 +37,7 @@ interface FleetAppProps { history: AppMountParameters['history']; kibanaVersion: string; extensions: UIExtensionsStorage; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } const FleetApp = ({ basepath, @@ -45,6 +46,7 @@ const FleetApp = ({ history, kibanaVersion, extensions, + setHeaderActionMenu, }: FleetAppProps) => { return ( - + ); @@ -64,7 +66,7 @@ const FleetApp = ({ export function renderApp( startServices: FleetStartServices, - { element, appBasePath, history }: AppMountParameters, + { element, appBasePath, history, setHeaderActionMenu }: AppMountParameters, config: FleetConfigType, kibanaVersion: string, extensions: UIExtensionsStorage @@ -77,6 +79,7 @@ export function renderApp( history={history} kibanaVersion={kibanaVersion} extensions={extensions} + setHeaderActionMenu={setHeaderActionMenu} />, element ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx index d707fd162ae020..f312ff374d792c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx @@ -6,145 +6,98 @@ */ import React from 'react'; -import styled from 'styled-components'; -import { - EuiTabs, - EuiTab, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiPortal, -} from '@elastic/eui'; +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import type { Section } from '../sections'; -import { SettingFlyout } from '../components'; -import { useLink, useConfig, useUrlModal } from '../hooks'; +import { useLink, useConfig } from '../hooks'; +import { WithHeaderLayout } from '../../../layouts'; interface Props { - showNav?: boolean; - showSettings?: boolean; section?: Section; children?: React.ReactNode; } -const Container = styled.div` - min-height: calc( - 100vh - ${(props) => parseFloat(props.theme.eui.euiHeaderHeightCompensation) * 2}px - ); - background: ${(props) => props.theme.eui.euiColorEmptyShade}; - display: flex; - flex-direction: column; -`; - -const Wrapper = styled.div` - display: flex; - flex-direction: column; - flex: 1; -`; - -const Nav = styled.nav` - background: ${(props) => props.theme.eui.euiColorEmptyShade}; - border-bottom: ${(props) => props.theme.eui.euiBorderThin}; - padding: ${(props) => - `${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL} ${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL}`}; - .euiTabs { - padding-left: 3px; - margin-left: -3px; - } -`; - -export const DefaultLayout: React.FunctionComponent = ({ - showNav = true, - showSettings = true, - section, - children, -}) => { +export const DefaultLayout: React.FunctionComponent = ({ section, children }) => { const { getHref } = useLink(); const { agents } = useConfig(); - const { modal, setModal, getModalHref } = useUrlModal(); return ( - <> - {modal === 'settings' && ( - - { - setModal(null); - }} - /> - - )} - - - - {showNav ? ( - - ) : null} - {children} - - - + + + + + +

+ +

+
+
+
+
+ + +

+ +

+
+
+ + } + tabs={[ + { + name: ( + + ), + isSelected: section === 'agents', + href: getHref('agent_list'), + disabled: !agents?.enabled, + 'data-test-subj': 'fleet-agents-tab', + }, + { + name: ( + + ), + isSelected: section === 'agent_policies', + href: getHref('policies_list'), + 'data-test-subj': 'fleet-agent-policies-tab', + }, + { + name: ( + + ), + isSelected: section === 'enrollment_tokens', + href: getHref('enrollment_tokens'), + 'data-test-subj': 'fleet-enrollment-tokens-tab', + }, + { + name: ( + + ), + isSelected: section === 'data_streams', + href: getHref('data_streams'), + 'data-test-subj': 'fleet-datastreams-tab', + }, + ]} + > + {children} +
); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx index c0ec811ce2bcd5..d8db44e28e4af9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx @@ -11,6 +11,8 @@ import { HashRouter as Router, Switch, Route } from 'react-router-dom'; import { FLEET_ROUTING_PATHS } from '../../constants'; import { useBreadcrumbs } from '../../hooks'; +import { DefaultLayout } from '../../layouts'; + import { AgentPolicyListPage } from './list_page'; import { AgentPolicyDetailsPage } from './details_page'; import { CreatePackagePolicyPage } from './create_package_policy_page'; @@ -32,7 +34,9 @@ export const AgentPolicyApp: React.FunctionComponent = () => { - + + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx index 48b9118d115666..10859e32f00805 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx @@ -9,7 +9,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { EuiTableActionsColumnType, EuiTableFieldDataColumnType } from '@elastic/eui'; import { EuiSpacer, - EuiText, EuiFlexGroup, EuiFlexItem, EuiButton, @@ -25,7 +24,6 @@ import { useHistory } from 'react-router-dom'; import type { AgentPolicy } from '../../../types'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants'; -import { WithHeaderLayout } from '../../../layouts'; import { useCapabilities, useGetAgentPolicies, @@ -41,37 +39,6 @@ import { LinkedAgentCount, AgentPolicyActionMenu } from '../components'; import { CreateAgentPolicyFlyout } from './components'; -const AgentPolicyListPageLayout: React.FunctionComponent = ({ children }) => ( - - - -

- -

-
-
- - -

- -

-
-
- - } - > - {children} -
-); - export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('policies_list'); const { getPath } = useLink(); @@ -246,7 +213,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { }; return ( - + <> {isCreateAgentPolicyFlyoutOpen ? ( { @@ -322,6 +289,6 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { sorting={{ sort: sorting }} onChange={onTableChange} /> - + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx index 6e0206603a458e..a599d726cedefe 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx @@ -101,7 +101,7 @@ export const AgentDetailsIntegration: React.FunctionComponent<{ })} > { () => ( - + { name: i18n.translate('xpack.fleet.agentDetails.subTabs.detailsTab', { defaultMessage: 'Agent details', }), - href: getHref('fleet_agent_details', { agentId, tabId: 'details' }), + href: getHref('agent_details', { agentId, tabId: 'details' }), isSelected: !tabId || tabId === 'details', }, { @@ -240,7 +235,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { name: i18n.translate('xpack.fleet.agentDetails.subTabs.logsTab', { defaultMessage: 'Logs', }), - href: getHref('fleet_agent_details', { agentId, tabId: 'logs' }), + href: getHref('agent_details_logs', { agentId, tabId: 'logs' }), isSelected: tabId === 'logs', }, ]; @@ -299,7 +294,7 @@ const AgentDetailsPageContent: React.FunctionComponent<{ agent: Agent; agentPolicy?: AgentPolicy; }> = ({ agent, agentPolicy }) => { - useBreadcrumbs('fleet_agent_details', { + useBreadcrumbs('agent_list', { agentHost: typeof agent.local_metadata.host === 'object' && typeof agent.local_metadata.host.hostname === 'string' @@ -309,13 +304,13 @@ const AgentDetailsPageContent: React.FunctionComponent<{ return ( { return ; }} /> { return ; }} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index 1beaf437ceb0e9..1d7b44ceefb7c6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -7,18 +7,20 @@ import React, { useState } from 'react'; import { + EuiButton, EuiFilterButton, EuiFilterGroup, EuiFilterSelectItem, EuiFlexGroup, EuiFlexItem, EuiPopover, + EuiPortal, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import type { AgentPolicy } from '../../../../types'; -import { SearchBar } from '../../../../components'; +import { AgentEnrollmentFlyout, SearchBar } from '../../../../components'; import { AGENTS_INDEX } from '../../../../constants'; const statusFilters = [ @@ -77,6 +79,8 @@ export const SearchAndFilterBar: React.FunctionComponent<{ showUpgradeable, onShowUpgradeableChange, }) => { + const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + // Policies state for filtering const [isAgentPoliciesFilterOpen, setIsAgentPoliciesFilterOpen] = useState(false); @@ -97,6 +101,15 @@ export const SearchAndFilterBar: React.FunctionComponent<{ return ( <> + {isEnrollmentFlyoutOpen ? ( + + setIsEnrollmentFlyoutOpen(false)} + /> + + ) : null} + {/* Search and filter bar */} @@ -207,6 +220,15 @@ export const SearchAndFilterBar: React.FunctionComponent<{ + + setIsEnrollmentFlyoutOpen(true)} + > + + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 672b8718c9cbe7..431c4da3efb5b2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -73,7 +73,7 @@ const RowActions = React.memo<{ const menuItems = [ @@ -146,7 +146,7 @@ function safeMetadata(val: any) { export const AgentListPage: React.FunctionComponent<{}> = () => { const { notifications } = useStartServices(); - useBreadcrumbs('fleet_agent_list'); + useBreadcrumbs('agent_list'); const { getHref } = useLink(); const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; const hasWriteCapabilites = useCapabilities().write; @@ -358,7 +358,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { defaultMessage: 'Host', }), render: (host: string, agent: Agent) => ( - + {safeMetadata(host)} ), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx deleted file mode 100644 index 67758282521b79..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx +++ /dev/null @@ -1,101 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButton, EuiPortal } from '@elastic/eui'; -import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; -import { useRouteMatch } from 'react-router-dom'; - -import { FLEET_ROUTING_PATHS } from '../../../constants'; -import { WithHeaderLayout } from '../../../layouts'; -import { useCapabilities, useLink, useGetAgentPolicies } from '../../../hooks'; -import { AgentEnrollmentFlyout } from '../../../components'; - -export const ListLayout: React.FunctionComponent<{}> = ({ children }) => { - const { getHref } = useLink(); - const hasWriteCapabilites = useCapabilities().write; - - // Agent enrollment flyout state - const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = React.useState(false); - - const headerRightColumn = hasWriteCapabilites ? ( - - - setIsEnrollmentFlyoutOpen(true)}> - - - - - ) : undefined; - const headerLeftColumn = ( - - - -

- -

-
-
- - -

- -

-
-
-
- ); - - const agentPoliciesRequest = useGetAgentPolicies({ - page: 1, - perPage: 1000, - }); - - const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []; - - const routeMatch = useRouteMatch(); - - return ( - , - isSelected: routeMatch.path === FLEET_ROUTING_PATHS.fleet_agent_list, - href: getHref('fleet_agent_list'), - }, - { - name: ( - - ), - isSelected: routeMatch.path === FLEET_ROUTING_PATHS.fleet_enrollment_tokens, - href: getHref('fleet_enrollment_tokens'), - }, - ] as unknown) as EuiTabProps[] - } - > - {isEnrollmentFlyoutOpen ? ( - - setIsEnrollmentFlyoutOpen(false)} - /> - - ) : null} - {children} - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx index 8dc9ad33962e0d..666d0887fe5103 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx @@ -34,6 +34,7 @@ import { } from '../../../hooks'; import type { EnrollmentAPIKey, GetAgentPoliciesResponseItem } from '../../../types'; import { SearchBar } from '../../../components/search_bar'; +import { DefaultLayout } from '../../../layouts'; import { ConfirmEnrollmentTokenDelete } from './components/confirm_delete_modal'; @@ -155,7 +156,7 @@ const DeleteButton: React.FunctionComponent<{ apiKey: EnrollmentAPIKey; refresh: }; export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { - useBreadcrumbs('fleet_enrollment_tokens'); + useBreadcrumbs('enrollment_tokens'); const [isModalOpen, setModalOpen] = useState(false); const [search, setSearch] = useState(''); const { pagination, setPagination, pageSizeOptions } = usePagination(); @@ -269,7 +270,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { ]; return ( - <> + {isModalOpen && ( = () => { setPagination(newPagination); }} /> - + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index dcb33e7662dc45..79b19b443cca14 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; +import { HashRouter as Router, Route, Switch } from 'react-router-dom'; import { FLEET_ROUTING_PATHS } from '../../constants'; import { Loading, Error } from '../../components'; @@ -18,20 +18,18 @@ import { useCapabilities, useGetSettings, } from '../../hooks'; -import { WithoutHeaderLayout } from '../../layouts'; +import { DefaultLayout, WithoutHeaderLayout } from '../../layouts'; import { AgentListPage } from './agent_list_page'; import { FleetServerRequirementPage, MissingESRequirementsPage } from './agent_requirements_page'; import { AgentDetailsPage } from './agent_details_page'; import { NoAccessPage } from './error_pages/no_access'; -import { EnrollmentTokenListPage } from './enrollment_token_list_page'; -import { ListLayout } from './components/list_layout'; import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal'; const REFRESH_INTERVAL_MS = 30000; -export const FleetApp: React.FunctionComponent = () => { - useBreadcrumbs('fleet'); +export const AgentsApp: React.FunctionComponent = () => { + useBreadcrumbs('agent_list'); const { agents } = useConfig(); const capabilities = useCapabilities(); @@ -110,16 +108,11 @@ export const FleetApp: React.FunctionComponent = () => { return ( - } - /> - + - - + + {fleetServerModalVisible && ( )} @@ -128,12 +121,7 @@ export const FleetApp: React.FunctionComponent = () => { ) : ( )} - - - - - - + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx index bc3a0229284dbd..c660d3ed297672 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { HashRouter as Router, Route, Switch } from 'react-router-dom'; import { FLEET_ROUTING_PATHS } from '../../constants'; +import { DefaultLayout } from '../../layouts'; import { DataStreamListPage } from './list_page'; @@ -17,7 +18,9 @@ export const DataStreamApp: React.FunctionComponent = () => { - + + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx index e805fb8f6f64ef..ac236578e6f58d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx @@ -10,7 +10,6 @@ import type { EuiTableActionsColumnType, EuiTableFieldDataColumnType } from '@el import { EuiBadge, EuiButton, - EuiText, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt, @@ -20,43 +19,11 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; import type { DataStream } from '../../../types'; -import { WithHeaderLayout } from '../../../layouts'; import { useGetDataStreams, useStartServices, usePagination, useBreadcrumbs } from '../../../hooks'; import { PackageIcon } from '../../../components'; import { DataStreamRowActions } from './components/data_stream_row_actions'; -const DataStreamListPageLayout: React.FunctionComponent = ({ children }) => ( - - - -

- -

-
-
- - -

- -

-
-
- - } - > - {children} -
-); - export const DataStreamListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('data_streams'); @@ -232,97 +199,95 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => { } return ( - - - ) : dataStreamsData && !dataStreamsData.data_streams.length ? ( - emptyPrompt - ) : ( + + ) : dataStreamsData && !dataStreamsData.data_streams.length ? ( + emptyPrompt + ) : ( + + ) + } + items={dataStreamsData ? dataStreamsData.data_streams : []} + itemId="index" + columns={columns} + pagination={{ + initialPageSize: pagination.pageSize, + pageSizeOptions, + }} + sorting={true} + search={{ + toolsRight: [ + resendRequest()} + > - ) - } - items={dataStreamsData ? dataStreamsData.data_streams : []} - itemId="index" - columns={columns} - pagination={{ - initialPageSize: pagination.pageSize, - pageSizeOptions, - }} - sorting={true} - search={{ - toolsRight: [ - resendRequest()} - > - - , - ], - box: { - placeholder: i18n.translate('xpack.fleet.dataStreamList.searchPlaceholderTitle', { - defaultMessage: 'Filter data streams', + , + ], + box: { + placeholder: i18n.translate('xpack.fleet.dataStreamList.searchPlaceholderTitle', { + defaultMessage: 'Filter data streams', + }), + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'dataset', + name: i18n.translate('xpack.fleet.dataStreamList.datasetColumnTitle', { + defaultMessage: 'Dataset', }), - incremental: true, + multiSelect: 'or', + operator: 'exact', + options: filterOptions.dataset, }, - filters: [ - { - type: 'field_value_selection', - field: 'dataset', - name: i18n.translate('xpack.fleet.dataStreamList.datasetColumnTitle', { - defaultMessage: 'Dataset', - }), - multiSelect: 'or', - operator: 'exact', - options: filterOptions.dataset, - }, - { - type: 'field_value_selection', - field: 'type', - name: i18n.translate('xpack.fleet.dataStreamList.typeColumnTitle', { - defaultMessage: 'Type', - }), - multiSelect: 'or', - operator: 'exact', - options: filterOptions.type, - }, - { - type: 'field_value_selection', - field: 'namespace', - name: i18n.translate('xpack.fleet.dataStreamList.namespaceColumnTitle', { - defaultMessage: 'Namespace', - }), - multiSelect: 'or', - operator: 'exact', - options: filterOptions.namespace, - }, - { - type: 'field_value_selection', - field: 'package', - name: i18n.translate('xpack.fleet.dataStreamList.integrationColumnTitle', { - defaultMessage: 'Integration', - }), - multiSelect: 'or', - operator: 'exact', - options: filterOptions.package, - }, - ], - }} - /> - + { + type: 'field_value_selection', + field: 'type', + name: i18n.translate('xpack.fleet.dataStreamList.typeColumnTitle', { + defaultMessage: 'Type', + }), + multiSelect: 'or', + operator: 'exact', + options: filterOptions.type, + }, + { + type: 'field_value_selection', + field: 'namespace', + name: i18n.translate('xpack.fleet.dataStreamList.namespaceColumnTitle', { + defaultMessage: 'Namespace', + }), + multiSelect: 'or', + operator: 'exact', + options: filterOptions.namespace, + }, + { + type: 'field_value_selection', + field: 'package', + name: i18n.translate('xpack.fleet.dataStreamList.integrationColumnTitle', { + defaultMessage: 'Integration', + }), + multiSelect: 'or', + operator: 'exact', + options: filterOptions.package, + }, + ], + }} + /> ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx index 810334e2df9ce6..b36fbf4bb815e7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx @@ -5,9 +5,8 @@ * 2.0. */ -export { IngestManagerOverview } from './overview'; export { AgentPolicyApp } from './agent_policy'; export { DataStreamApp } from './data_stream'; -export { FleetApp } from './agents'; +export { AgentsApp } from './agents'; -export type Section = 'overview' | 'agent_policy' | 'fleet' | 'data_stream'; +export type Section = 'agents' | 'agent_policies' | 'enrollment_tokens' | 'data_streams'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx deleted file mode 100644 index 79a4f08faa7522..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx +++ /dev/null @@ -1,78 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { - EuiFlexItem, - EuiI18nNumber, - EuiDescriptionListTitle, - EuiDescriptionListDescription, -} from '@elastic/eui'; - -import { SO_SEARCH_LIMIT } from '../../../constants'; -import { useLink, useGetPackagePolicies } from '../../../hooks'; -import type { AgentPolicy } from '../../../types'; -import { Loading } from '../../agents/components'; - -import { OverviewStats } from './overview_stats'; -import { OverviewPanel } from './overview_panel'; - -export const OverviewPolicySection: React.FC<{ agentPolicies: AgentPolicy[] }> = ({ - agentPolicies, -}) => { - const { getHref } = useLink(); - const packagePoliciesRequest = useGetPackagePolicies({ - page: 1, - perPage: SO_SEARCH_LIMIT, - }); - - return ( - - - - {packagePoliciesRequest.isLoading ? ( - - ) : ( - <> - - - - - - - - - - - - - - )} - - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx deleted file mode 100644 index d69306969c78c3..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx +++ /dev/null @@ -1,87 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { - EuiI18nNumber, - EuiDescriptionListTitle, - EuiDescriptionListDescription, - EuiFlexItem, -} from '@elastic/eui'; - -import { useLink, useGetAgentStatus } from '../../../hooks'; -import { Loading } from '../../agents/components'; - -import { OverviewPanel } from './overview_panel'; -import { OverviewStats } from './overview_stats'; - -export const OverviewAgentSection = () => { - const { getHref } = useLink(); - const agentStatusRequest = useGetAgentStatus({}); - - return ( - - - - {agentStatusRequest.isLoading ? ( - - ) : ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - )} - - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx deleted file mode 100644 index b51be3fdd20e56..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { - EuiFlexItem, - EuiI18nNumber, - EuiDescriptionListTitle, - EuiDescriptionListDescription, -} from '@elastic/eui'; - -import { useLink, useGetDataStreams, useStartServices } from '../../../hooks'; -import { Loading } from '../../agents/components'; - -import { OverviewPanel } from './overview_panel'; -import { OverviewStats } from './overview_stats'; - -export const OverviewDatastreamSection: React.FC = () => { - const { getHref } = useLink(); - const datastreamRequest = useGetDataStreams(); - const { - data: { fieldFormats }, - } = useStartServices(); - - const total = datastreamRequest.data?.data_streams?.length ?? 0; - let sizeBytes = 0; - const namespaces = new Set(); - if (datastreamRequest.data) { - datastreamRequest.data.data_streams.forEach((val) => { - namespaces.add(val.namespace); - sizeBytes += val.size_in_bytes; - }); - } - - let size: string; - try { - const formatter = fieldFormats.getInstance('bytes'); - size = formatter.convert(sizeBytes); - } catch (e) { - size = `${sizeBytes}b`; - } - - return ( - - - - {datastreamRequest.isLoading ? ( - - ) : ( - <> - - - - - - - - - - - - - - - - {size} - - )} - - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx deleted file mode 100644 index 5ada8e298507cb..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { - EuiFlexItem, - EuiI18nNumber, - EuiDescriptionListTitle, - EuiDescriptionListDescription, -} from '@elastic/eui'; - -import { useLink, useGetPackages } from '../../../hooks'; -import { Loading } from '../../agents/components'; -import { installationStatuses } from '../../../../../../common/constants'; - -import { OverviewStats } from './overview_stats'; -import { OverviewPanel } from './overview_panel'; - -export const OverviewIntegrationSection: React.FC = () => { - const { getHref } = useLink(); - const packagesRequest = useGetPackages(); - const res = packagesRequest.data?.response; - const total = res?.length ?? 0; - const installed = res?.filter((p) => p.status === installationStatuses.Installed)?.length ?? 0; - const updatablePackages = - res?.filter( - (item) => 'savedObject' in item && item.version > item.savedObject.attributes.version - )?.length ?? 0; - return ( - - - - {packagesRequest.isLoading ? ( - - ) : ( - <> - - - - - - - - - - - - - - - - - - - - )} - - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx deleted file mode 100644 index c402bc15f7b026..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx +++ /dev/null @@ -1,74 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import styled from 'styled-components'; -import { - EuiPanel, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiIconTip, - EuiButtonEmpty, -} from '@elastic/eui'; - -const StyledPanel = styled(EuiPanel).attrs((props) => ({ - paddingSize: 'm', -}))` - header { - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; - margin: -${(props) => props.theme.eui.paddingSizes.m} -${(props) => - props.theme.eui.paddingSizes.m} - ${(props) => props.theme.eui.paddingSizes.m}; - padding: ${(props) => props.theme.eui.paddingSizes.s} - ${(props) => props.theme.eui.paddingSizes.m}; - } - - h2 { - padding: ${(props) => props.theme.eui.paddingSizes.xs} 0; - } -`; - -interface OverviewPanelProps { - title: string; - tooltip: string; - linkToText: string; - linkTo: string; - children: React.ReactNode; -} - -export const OverviewPanel = ({ - title, - tooltip, - linkToText, - linkTo, - children, -}: OverviewPanelProps) => { - return ( - -
- - - -

{title}

-
-
- - - -
- - {linkToText} - -
- {children} -
- ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx deleted file mode 100644 index acb94e4b05695d..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx +++ /dev/null @@ -1,24 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import styled from 'styled-components'; -import { EuiDescriptionList } from '@elastic/eui'; - -export const OverviewStats = styled(EuiDescriptionList).attrs((props) => ({ - compressed: true, - textStyle: 'reverse', - type: 'column', -}))` - & > * { - margin-top: ${(props) => props.theme.eui.paddingSizes.s} !important; - - &:first-child, - &:nth-child(2) { - margin-top: 0 !important; - } - } -`; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx deleted file mode 100644 index f905fd1c89da27..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx +++ /dev/null @@ -1,110 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { - EuiButton, - EuiBetaBadge, - EuiText, - EuiTitle, - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { WithHeaderLayout } from '../../layouts'; -import { useGetAgentPolicies, useBreadcrumbs } from '../../hooks'; -import { AgentEnrollmentFlyout } from '../../components'; - -import { OverviewAgentSection } from './components/agent_section'; -import { OverviewPolicySection } from './components/agent_policy_section'; -import { OverviewIntegrationSection } from './components/integration_section'; -import { OverviewDatastreamSection } from './components/datastream_section'; - -export const IngestManagerOverview: React.FunctionComponent = () => { - useBreadcrumbs('overview'); - - // Agent enrollment flyout state - const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); - - // Agent policies required for enrollment flyout - const agentPoliciesRequest = useGetAgentPolicies({ - page: 1, - perPage: 1000, - }); - const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []; - - return ( - - - - - -

- -

-
-
- - - - -
-
- - -

- -

-
-
- - } - rightColumn={ - - - setIsEnrollmentFlyoutOpen(true)}> - - - - - } - > - {isEnrollmentFlyoutOpen && ( - setIsEnrollmentFlyoutOpen(false)} - /> - )} - - - - - - - -
- ); -}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx index 4c1ff4972b89e2..98b8e9515e689c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React, { memo } from 'react'; -import { EuiText, EuiBetaBadge } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useLink } from '../../../hooks'; @@ -30,15 +30,6 @@ export const DefaultLayout: React.FunctionComponent = memo(({ section, ch

{' '} - } - tooltipContent={ - - } - />

} diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx index 2bb8586a11503d..e7045173f1257e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx @@ -36,7 +36,7 @@ const DefaultMissingRequirements = () => { defaultMessage="Before enrolling agents, {link}." values={{ link: ( - + 0 ? ( diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index 3c9c0e57596151..326cfd804bd570 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -13,8 +13,7 @@ export type StaticPage = | 'integrations_installed' | 'policies' | 'policies_list' - | 'fleet' - | 'fleet_enrollment_tokens' + | 'enrollment_tokens' | 'data_streams'; export type DynamicPage = @@ -27,8 +26,9 @@ export type DynamicPage = | 'add_integration_from_policy' | 'add_integration_to_policy' | 'edit_integration' - | 'fleet_agent_list' - | 'fleet_agent_details'; + | 'agent_list' + | 'agent_details' + | 'agent_details_logs'; export type Page = StaticPage | DynamicPage; @@ -42,20 +42,21 @@ export const INTEGRATIONS_BASE_PATH = '/app/integrations'; // If routing paths are changed here, please also check to see if // `pagePathGetters()`, below, needs any modifications export const FLEET_ROUTING_PATHS = { - overview: '/', + fleet: '/:tabId', + agents: '/agents', + agent_details: '/agents/:agentId/:tabId?', + agent_details_logs: '/agents/:agentId/logs', policies: '/policies', policies_list: '/policies', policy_details: '/policies/:policyId/:tabId?', policy_details_settings: '/policies/:policyId/settings', - add_integration_from_policy: '/policies/:policyId/add-integration', - add_integration_to_policy: '/integrations/:pkgkey/add-integration/:integration?', edit_integration: '/policies/:policyId/edit-integration/:packagePolicyId', - fleet: '/fleet', - fleet_agent_list: '/fleet/agents', - fleet_agent_details: '/fleet/agents/:agentId/:tabId?', - fleet_agent_details_logs: '/fleet/agents/:agentId/logs', - fleet_enrollment_tokens: '/fleet/enrollment-tokens', + add_integration_from_policy: '/policies/:policyId/add-integration', + enrollment_tokens: '/enrollment-tokens', data_streams: '/data-streams', + + // TODO: Move this to the integrations app + add_integration_to_policy: '/integrations/:pkgkey/add-integration/:integration?', }; export const INTEGRATIONS_ROUTING_PATHS = { @@ -120,15 +121,12 @@ export const pagePathGetters: { FLEET_BASE_PATH, `/policies/${policyId}/edit-integration/${packagePolicyId}`, ], - fleet: () => [FLEET_BASE_PATH, '/fleet'], - fleet_agent_list: ({ kuery }) => [ - FLEET_BASE_PATH, - `/fleet/agents${kuery ? `?kuery=${kuery}` : ''}`, - ], - fleet_agent_details: ({ agentId, tabId, logQuery }) => [ + agent_list: ({ kuery }) => [FLEET_BASE_PATH, `/agents${kuery ? `?kuery=${kuery}` : ''}`], + agent_details: ({ agentId, tabId, logQuery }) => [ FLEET_BASE_PATH, - `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}${logQuery ? `?_q=${logQuery}` : ''}`, + `/agents/${agentId}${tabId ? `/${tabId}` : ''}${logQuery ? `?_q=${logQuery}` : ''}`, ], - fleet_enrollment_tokens: () => [FLEET_BASE_PATH, '/fleet/enrollment-tokens'], + agent_details_logs: ({ agentId }) => [FLEET_BASE_PATH, `/agents/${agentId}/logs`], + enrollment_tokens: () => [FLEET_BASE_PATH, '/enrollment-tokens'], data_streams: () => [FLEET_BASE_PATH, '/data-streams'], }; diff --git a/x-pack/plugins/fleet/public/layouts/without_header.tsx b/x-pack/plugins/fleet/public/layouts/without_header.tsx index 220ee592d7d07a..d9481d44359c25 100644 --- a/x-pack/plugins/fleet/public/layouts/without_header.tsx +++ b/x-pack/plugins/fleet/public/layouts/without_header.tsx @@ -11,6 +11,13 @@ import { EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; export const Wrapper = styled.div` background-color: ${(props) => props.theme.eui.euiColorEmptyShade}; + + // HACK: Kibana introduces a div element around the app component that results in us + // being unable to stretch this Wrapper to full height via flex: 1. This calc sets + // the min height to the viewport size minus the height of the two global Kibana headers. + min-height: calc( + 100vh - ${(props) => parseFloat(props.theme.eui.euiHeaderHeightCompensation) * 2}px + ); `; export const Page = styled(EuiPage)` diff --git a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts index 16fa34e2d0b3d7..5d1567936bcb00 100644 --- a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts +++ b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts @@ -8,6 +8,7 @@ import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { licensingMock } from '../../../licensing/public/mocks'; import { homePluginMock } from '../../../../../src/plugins/home/public/mocks'; +import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; import type { MockedFleetSetupDeps, MockedFleetStartDeps } from './types'; @@ -22,5 +23,6 @@ export const createSetupDepsMock = (): MockedFleetSetupDeps => { export const createStartDepsMock = (): MockedFleetStartDeps => { return { data: dataPluginMock.createStartContract(), + navigation: navigationPluginMock.createStartContract(), }; }; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index f9515ca925a4ab..7b71b210068648 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -14,6 +14,8 @@ import type { } from 'src/core/public'; import { i18n } from '@kbn/i18n'; +import type { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; + import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '../../../../src/core/public'; import type { DataPublicPluginSetup, @@ -64,6 +66,7 @@ export interface FleetSetupDeps { export interface FleetStartDeps { data: DataPublicPluginStart; + navigation: NavigationPublicPluginStart; } export interface FleetStartServices extends CoreStart, FleetStartDeps { diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx index 23277976968a98..23eaaeac1439d4 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx @@ -132,7 +132,7 @@ const ActionResultsSummaryComponent: React.FC = ({ diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index affc600847284c..6ff60d30d23bf7 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -66,7 +66,7 @@ const ResultsTableComponent: React.FC = ({ const getFleetAppUrl = useCallback( (agentId) => getUrlForApp('fleet', { - path: `#` + pagePathGetters.fleet_agent_details({ agentId }), + path: `#` + pagePathGetters.agent_details({ agentId }), }), [getUrlForApp] ); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 31069b1939ce98..e03427671798da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -138,13 +138,13 @@ export const useEndpointActionItems = ( navigateAppId: 'fleet', navigateOptions: { path: `#${ - pagePathGetters.fleet_agent_details({ + pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] }`, }, href: `${getUrlForApp('fleet')}#${ - pagePathGetters.fleet_agent_details({ + pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] }`, @@ -162,13 +162,13 @@ export const useEndpointActionItems = ( navigateAppId: 'fleet', navigateOptions: { path: `#${ - pagePathGetters.fleet_agent_details({ + pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] }/activity?openReassignFlyout=true`, }, href: `${getUrlForApp('fleet')}#${ - pagePathGetters.fleet_agent_details({ + pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] }/activity?openReassignFlyout=true`, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 86f1e32e751eeb..1ac5c289c87cf7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -1108,13 +1108,13 @@ describe('when on the endpoint list page', () => { }); it('navigates to the Ingest Agent Details page', async () => { const agentDetailsLink = await renderResult.findByTestId('agentDetailsLink'); - expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/fleet/agents/${agentId}`); + expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/agents/${agentId}`); }); it('navigates to the Ingest Agent Details page with policy reassign', async () => { const agentPolicyReassignLink = await renderResult.findByTestId('agentPolicyReassignLink'); expect(agentPolicyReassignLink.getAttribute('href')).toEqual( - `/app/fleet#/fleet/agents/${agentId}/activity?openReassignFlyout=true` + `/app/fleet#/agents/${agentId}/activity?openReassignFlyout=true` ); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 410afb4684cd54..d1dab3dd07a7e3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -515,12 +515,12 @@ export const EndpointList = () => { agentsLink: ( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts index 8c1eb611cb5a1a..c54c12981c7710 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts @@ -156,9 +156,7 @@ export const isolationRequestHandler = function ( commentLines.push(`${isolate ? 'I' : 'Uni'}solate action was sent to the following Agents:`); // lines of markdown links, inside a code block - commentLines.push( - `${agentIDs.map((a) => `- [${a}](/app/fleet#/fleet/agents/${a})`).join('\n')}` - ); + commentLines.push(`${agentIDs.map((a) => `- [${a}](/app/fleet#/agents/${a})`).join('\n')}`); if (req.body.comment) { commentLines.push(`\n\nWith Comment:\n> ${req.body.comment}`); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fb936a58387816..db382a677fbe82 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8829,7 +8829,6 @@ "xpack.fleet.agentList.addButton": "エージェントの追加", "xpack.fleet.agentList.agentUpgradeLabel": "アップグレードが利用可能です", "xpack.fleet.agentList.clearFiltersLinkText": "フィルターを消去", - "xpack.fleet.agentList.enrollButton": "エージェントの追加", "xpack.fleet.agentList.errorFetchingDataTitle": "エージェントの取り込みエラー", "xpack.fleet.agentList.forceUnenrollOneButton": "強制的に登録解除する", "xpack.fleet.agentList.hostColumnTitle": "ホスト", @@ -8903,8 +8902,6 @@ "xpack.fleet.agentPolicyList.noAgentPoliciesPrompt": "エージェントポリシーがありません", "xpack.fleet.agentPolicyList.noFilteredAgentPoliciesPrompt": "エージェントポリシーが見つかりません。{clearFiltersLink}", "xpack.fleet.agentPolicyList.packagePoliciesCountColumnTitle": "統合", - "xpack.fleet.agentPolicyList.pageSubtitle": "エージェントポリシーを使用すると、エージェントとエージェントが収集するデータを管理できます。", - "xpack.fleet.agentPolicyList.pageTitle": "エージェントポリシー", "xpack.fleet.agentPolicyList.reloadAgentPoliciesButtonText": "再読み込み", "xpack.fleet.agentPolicyList.updatedOnColumnTitle": "最終更新日", "xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip": "このポリシーはFleet外で管理されます。このポリシーに関連するほとんどのアクションは使用できません。", @@ -8914,8 +8911,6 @@ "xpack.fleet.agentReassignPolicy.flyoutTitle": "新しいエージェントポリシーを割り当てる", "xpack.fleet.agentReassignPolicy.selectPolicyLabel": "エージェントポリシー", "xpack.fleet.agentReassignPolicy.successSingleNotificationTitle": "エージェントポリシーが再割り当てされました", - "xpack.fleet.agents.pageSubtitle": "ポリシーの更新を管理し、任意のサイズのエージェントのグループにデプロイします。", - "xpack.fleet.agents.pageTitle": "エージェント", "xpack.fleet.agentsInitializationErrorMessageTitle": "Elasticエージェントの集中管理を初期化できません", "xpack.fleet.agentStatus.healthyLabel": "正常", "xpack.fleet.agentStatus.inactiveLabel": "非アクティブ", @@ -8924,13 +8919,10 @@ "xpack.fleet.agentStatus.updatingLabel": "更新中", "xpack.fleet.appNavigation.agentsLinkText": "エージェント", "xpack.fleet.appNavigation.dataStreamsLinkText": "データストリーム", - "xpack.fleet.appNavigation.overviewLinkText": "概要", "xpack.fleet.appNavigation.policiesLinkText": "ポリシー", "xpack.fleet.appNavigation.sendFeedbackButton": "フィードバックを送信", "xpack.fleet.appNavigation.settingsButton": "Fleet 設定", "xpack.fleet.appTitle": "Fleet", - "xpack.fleet.betaBadge.labelText": "ベータ", - "xpack.fleet.betaBadge.tooltipText": "このプラグインは本番環境用ではありません。バグについてはディスカッションフォーラムで報告してください。", "xpack.fleet.breadcrumbs.addPackagePolicyPageTitle": "統合の追加", "xpack.fleet.breadcrumbs.agentsPageTitle": "エージェント", "xpack.fleet.breadcrumbs.allIntegrationsPageTitle": "すべて", @@ -8939,7 +8931,6 @@ "xpack.fleet.breadcrumbs.editPackagePolicyPageTitle": "統合の編集", "xpack.fleet.breadcrumbs.enrollmentTokensPageTitle": "登録トークン", "xpack.fleet.breadcrumbs.installedIntegrationsPageTitle": "インストール済み", - "xpack.fleet.breadcrumbs.overviewPageTitle": "概要", "xpack.fleet.breadcrumbs.policiesPageTitle": "ポリシー", "xpack.fleet.config.invalidPackageVersionError": "有効なサーバーまたはキーワード「latest」でなければなりません", "xpack.fleet.copyAgentPolicy.confirmModal.cancelButtonLabel": "キャンセル", @@ -8998,8 +8989,6 @@ "xpack.fleet.dataStreamList.namespaceColumnTitle": "名前空間", "xpack.fleet.dataStreamList.noDataStreamsPrompt": "データストリームがありません", "xpack.fleet.dataStreamList.noFilteredDataStreamsMessage": "一致するデータストリームが見つかりません", - "xpack.fleet.dataStreamList.pageSubtitle": "エージェントが作成したデータを管理します。", - "xpack.fleet.dataStreamList.pageTitle": "データストリーム", "xpack.fleet.dataStreamList.reloadDataStreamsButtonText": "再読み込み", "xpack.fleet.dataStreamList.searchPlaceholderTitle": "データストリームをフィルター", "xpack.fleet.dataStreamList.sizeColumnTitle": "サイズ", @@ -9188,8 +9177,6 @@ "xpack.fleet.integrations.updatePackage.updatePackageButtonLabel": "最新バージョンに更新", "xpack.fleet.invalidLicenseDescription": "現在のライセンスは期限切れです。登録されたビートエージェントは引き続き動作しますが、Elastic Fleet インターフェイスにアクセスするには有効なライセンスが必要です。", "xpack.fleet.invalidLicenseTitle": "ライセンスの期限切れ", - "xpack.fleet.listTabs.agentTitle": "エージェント", - "xpack.fleet.listTabs.enrollmentTokensTitle": "登録トークン", "xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "名前空間に無効な文字が含まれています", "xpack.fleet.namespaceValidation.lowercaseErrorMessage": "名前空間は小文字で指定する必要があります", "xpack.fleet.namespaceValidation.requiredErrorMessage": "名前空間は必須です", @@ -9202,33 +9189,8 @@ "xpack.fleet.noAccess.accessDeniedDescription": "Elastic Fleet にアクセスする権限がありません。Elastic Fleet を使用するには、このアプリケーションの読み取り権または全権を含むユーザーロールが必要です。", "xpack.fleet.noAccess.accessDeniedTitle": "アクセスが拒否されました", "xpack.fleet.oldAppTitle": "Ingest Manager", - "xpack.fleet.overviewAgentActiveTitle": "アクティブ", - "xpack.fleet.overviewAgentErrorTitle": "エラー", - "xpack.fleet.overviewAgentOfflineTitle": "オフライン", - "xpack.fleet.overviewAgentTotalTitle": "合計エージェント数", - "xpack.fleet.overviewDatastreamNamespacesTitle": "名前空間", - "xpack.fleet.overviewDatastreamSizeTitle": "合計サイズ", - "xpack.fleet.overviewDatastreamTotalTitle": "データストリーム", - "xpack.fleet.overviewIntegrationsInstalledTitle": "インストール済み", - "xpack.fleet.overviewIntegrationsTotalTitle": "合計利用可能数", - "xpack.fleet.overviewIntegrationsUpdatesAvailableTitle": "更新が可能です", - "xpack.fleet.overviewPackagePolicyTitle": "使用済みの統合", - "xpack.fleet.overviewPageAgentsPanelTitle": "エージェント", - "xpack.fleet.overviewPageDataStreamsPanelAction": "データストリームを表示", - "xpack.fleet.overviewPageDataStreamsPanelTitle": "データストリーム", - "xpack.fleet.overviewPageDataStreamsPanelTooltip": "エージェントが収集するデータはさまざまなデータストリームに整理されます。", - "xpack.fleet.overviewPageEnrollAgentButton": "エージェントの追加", - "xpack.fleet.overviewPageFleetPanelAction": "エージェントを表示", - "xpack.fleet.overviewPageFleetPanelTooltip": "Fleetを使用して、中央の場所からエージェントを登録し、ポリシーを管理します。", - "xpack.fleet.overviewPageIntegrationsPanelAction": "統合を表示", - "xpack.fleet.overviewPageIntegrationsPanelTitle": "統合", - "xpack.fleet.overviewPageIntegrationsPanelTooltip": "Elastic Stackの統合を参照し、インストールします。統合をエージェントポリシーに追加し、データの送信を開始します。", - "xpack.fleet.overviewPagePoliciesPanelAction": "ポリシーを表示", - "xpack.fleet.overviewPagePoliciesPanelTitle": "エージェントポリシー", - "xpack.fleet.overviewPagePoliciesPanelTooltip": "エージェントポリシーを使用すると、エージェントが収集するデータを管理できます。", "xpack.fleet.overviewPageSubtitle": "Elasticエージェントとポリシーを中央の場所で管理します。", "xpack.fleet.overviewPageTitle": "Fleet", - "xpack.fleet.overviewPolicyTotalTitle": "合計利用可能数", "xpack.fleet.packagePolicyInputOverrideError": "パッケージ{packageName}には入力タイプ{inputType}が存在しません。", "xpack.fleet.packagePolicyStreamOverrideError": "パッケージ{packageName}の{inputType}にはデータストリーム{streamSet}が存在しません", "xpack.fleet.packagePolicyStreamVarOverrideError": "パッケージ{packageName}の{inputType}の{streamSet}にはVar {varName}が存在しません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 998b2a4c672872..2f86597f4c5c50 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8906,7 +8906,6 @@ "xpack.fleet.agentList.addButton": "添加代理", "xpack.fleet.agentList.agentUpgradeLabel": "升级可用", "xpack.fleet.agentList.clearFiltersLinkText": "清除筛选", - "xpack.fleet.agentList.enrollButton": "添加代理", "xpack.fleet.agentList.errorFetchingDataTitle": "获取代理时出错", "xpack.fleet.agentList.forceUnenrollOneButton": "强制取消注册", "xpack.fleet.agentList.hostColumnTitle": "主机", @@ -8981,8 +8980,6 @@ "xpack.fleet.agentPolicyList.noAgentPoliciesPrompt": "无代理策略", "xpack.fleet.agentPolicyList.noFilteredAgentPoliciesPrompt": "找不到任何代理策略。{clearFiltersLink}", "xpack.fleet.agentPolicyList.packagePoliciesCountColumnTitle": "集成", - "xpack.fleet.agentPolicyList.pageSubtitle": "使用代理策略管理代理及其收集的数据。", - "xpack.fleet.agentPolicyList.pageTitle": "代理策略", "xpack.fleet.agentPolicyList.reloadAgentPoliciesButtonText": "重新加载", "xpack.fleet.agentPolicyList.updatedOnColumnTitle": "上次更新时间", "xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip": "此策略是在 Fleet 外进行管理的。与此策略相关的操作多数不可用。", @@ -8994,8 +8991,6 @@ "xpack.fleet.agentReassignPolicy.policyDescription": "选定代理策略将收集 {count, plural, other {{countValue} 个集成} }的数据:", "xpack.fleet.agentReassignPolicy.selectPolicyLabel": "代理策略", "xpack.fleet.agentReassignPolicy.successSingleNotificationTitle": "代理策略已重新分配", - "xpack.fleet.agents.pageSubtitle": "管理策略更新并将其部署到一组任意大小的代理。", - "xpack.fleet.agents.pageTitle": "代理", "xpack.fleet.agentsInitializationErrorMessageTitle": "无法为 Elastic 代理初始化集中管理", "xpack.fleet.agentStatus.healthyLabel": "运行正常", "xpack.fleet.agentStatus.inactiveLabel": "非活动", @@ -9004,13 +8999,10 @@ "xpack.fleet.agentStatus.updatingLabel": "正在更新", "xpack.fleet.appNavigation.agentsLinkText": "代理", "xpack.fleet.appNavigation.dataStreamsLinkText": "数据流", - "xpack.fleet.appNavigation.overviewLinkText": "概览", "xpack.fleet.appNavigation.policiesLinkText": "策略", "xpack.fleet.appNavigation.sendFeedbackButton": "发送反馈", "xpack.fleet.appNavigation.settingsButton": "Fleet 设置", "xpack.fleet.appTitle": "Fleet", - "xpack.fleet.betaBadge.labelText": "公测版", - "xpack.fleet.betaBadge.tooltipText": "不推荐在生产环境中使用此插件。请在我们讨论论坛中报告错误。", "xpack.fleet.breadcrumbs.addPackagePolicyPageTitle": "添加集成", "xpack.fleet.breadcrumbs.agentsPageTitle": "代理", "xpack.fleet.breadcrumbs.allIntegrationsPageTitle": "全部", @@ -9019,7 +9011,6 @@ "xpack.fleet.breadcrumbs.editPackagePolicyPageTitle": "编辑集成", "xpack.fleet.breadcrumbs.enrollmentTokensPageTitle": "注册令牌", "xpack.fleet.breadcrumbs.installedIntegrationsPageTitle": "已安装", - "xpack.fleet.breadcrumbs.overviewPageTitle": "概览", "xpack.fleet.breadcrumbs.policiesPageTitle": "策略", "xpack.fleet.config.invalidPackageVersionError": "必须是有效的 semver 或关键字 `latest`", "xpack.fleet.copyAgentPolicy.confirmModal.cancelButtonLabel": "取消", @@ -9080,8 +9071,6 @@ "xpack.fleet.dataStreamList.namespaceColumnTitle": "命名空间", "xpack.fleet.dataStreamList.noDataStreamsPrompt": "无数据流", "xpack.fleet.dataStreamList.noFilteredDataStreamsMessage": "找不到匹配的数据流", - "xpack.fleet.dataStreamList.pageSubtitle": "管理您的代理创建的数据。", - "xpack.fleet.dataStreamList.pageTitle": "数据流", "xpack.fleet.dataStreamList.reloadDataStreamsButtonText": "重新加载", "xpack.fleet.dataStreamList.searchPlaceholderTitle": "筛选数据流", "xpack.fleet.dataStreamList.sizeColumnTitle": "大小", @@ -9274,8 +9263,6 @@ "xpack.fleet.integrations.updatePackage.updatePackageButtonLabel": "更新到最新版本", "xpack.fleet.invalidLicenseDescription": "您当前的许可证已过期。已注册 Beats 代理将继续工作,但您需要有效的许可证,才能访问 Elastic Fleet 界面。", "xpack.fleet.invalidLicenseTitle": "已过期许可证", - "xpack.fleet.listTabs.agentTitle": "代理", - "xpack.fleet.listTabs.enrollmentTokensTitle": "注册令牌", "xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "命名空间包含无效字符", "xpack.fleet.namespaceValidation.lowercaseErrorMessage": "命名空间必须小写", "xpack.fleet.namespaceValidation.requiredErrorMessage": "“命名空间”必填", @@ -9288,33 +9275,8 @@ "xpack.fleet.noAccess.accessDeniedDescription": "您无权访问 Elastic Fleet。要使用 Elastic Fleet,您需要包含此应用程序读取权限或所有权限的用户角色。", "xpack.fleet.noAccess.accessDeniedTitle": "访问被拒绝", "xpack.fleet.oldAppTitle": "采集管理器", - "xpack.fleet.overviewAgentActiveTitle": "活动", - "xpack.fleet.overviewAgentErrorTitle": "错误", - "xpack.fleet.overviewAgentOfflineTitle": "脱机", - "xpack.fleet.overviewAgentTotalTitle": "代理总数", - "xpack.fleet.overviewDatastreamNamespacesTitle": "命名空间", - "xpack.fleet.overviewDatastreamSizeTitle": "总大小", - "xpack.fleet.overviewDatastreamTotalTitle": "数据流", - "xpack.fleet.overviewIntegrationsInstalledTitle": "已安装", - "xpack.fleet.overviewIntegrationsTotalTitle": "可用总计", - "xpack.fleet.overviewIntegrationsUpdatesAvailableTitle": "可用更新", - "xpack.fleet.overviewPackagePolicyTitle": "已使用的集成", - "xpack.fleet.overviewPageAgentsPanelTitle": "代理", - "xpack.fleet.overviewPageDataStreamsPanelAction": "查看数据流", - "xpack.fleet.overviewPageDataStreamsPanelTitle": "数据流", - "xpack.fleet.overviewPageDataStreamsPanelTooltip": "您的代理收集的数据组织到各种数据流中。", - "xpack.fleet.overviewPageEnrollAgentButton": "添加代理", - "xpack.fleet.overviewPageFleetPanelAction": "查看代理", - "xpack.fleet.overviewPageFleetPanelTooltip": "使用 Fleet 注册代理并从中央位置管理其策略。", - "xpack.fleet.overviewPageIntegrationsPanelAction": "查看集成", - "xpack.fleet.overviewPageIntegrationsPanelTitle": "集成", - "xpack.fleet.overviewPageIntegrationsPanelTooltip": "浏览并安装适用于 Elastic Stack 的集成。将集成添加到您的代理策略,以开始发送数据。", - "xpack.fleet.overviewPagePoliciesPanelAction": "查看策略", - "xpack.fleet.overviewPagePoliciesPanelTitle": "代理策略", - "xpack.fleet.overviewPagePoliciesPanelTooltip": "使用代理策略控制您的代理收集的数据。", "xpack.fleet.overviewPageSubtitle": "在集中位置管理 Elastic 代理及其策略。", "xpack.fleet.overviewPageTitle": "Fleet", - "xpack.fleet.overviewPolicyTotalTitle": "可用总计", "xpack.fleet.packagePolicyInputOverrideError": "输入类型 {inputType} 在软件包 {packageName} 上不存在", "xpack.fleet.packagePolicyStreamOverrideError": "数据流 {streamSet} 在软件包 {packageName} 的 {inputType} 上不存在", "xpack.fleet.packagePolicyStreamVarOverrideError": "变量 {varName} 在软件包 {packageName} 的 {inputType} 的 {streamSet} 上不存在", diff --git a/x-pack/test/fleet_functional/apps/fleet/agents_page.ts b/x-pack/test/fleet_functional/apps/fleet/agents_page.ts new file mode 100644 index 00000000000000..515eaa65f5310a --- /dev/null +++ b/x-pack/test/fleet_functional/apps/fleet/agents_page.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { agentsPage } = getPageObjects(['agentsPage']); + + describe('When in the Fleet application', function () { + this.tags(['ciGroup7']); + + describe('and on the agents page', () => { + before(async () => { + await agentsPage.navigateToAgentsPage(); + }); + + it('should show the agents tab', async () => { + await agentsPage.agentsTabExistsOrFail(); + }); + + it('should show the agent policies tab', async () => { + await agentsPage.agentPoliciesTabExistsOrFail(); + }); + + it('should show the enrollment tokens tab', async () => { + await agentsPage.enrollmentTokensTabExistsOrFail(); + }); + + it('should show the data streams tab', async () => { + await agentsPage.dataStreamsTabExistsOrFail(); + }); + }); + }); +} diff --git a/x-pack/test/fleet_functional/apps/fleet/index.ts b/x-pack/test/fleet_functional/apps/fleet/index.ts index 23a070cb799340..ec16e2d8571831 100644 --- a/x-pack/test/fleet_functional/apps/fleet/index.ts +++ b/x-pack/test/fleet_functional/apps/fleet/index.ts @@ -12,6 +12,6 @@ export default function (providerContext: FtrProviderContext) { describe('endpoint', function () { this.tags('ciGroup7'); - loadTestFile(require.resolve('./overview_page')); + loadTestFile(require.resolve('./agents_page')); }); } diff --git a/x-pack/test/fleet_functional/apps/fleet/overview_page.ts b/x-pack/test/fleet_functional/apps/fleet/overview_page.ts deleted file mode 100644 index 3d3b069665448b..00000000000000 --- a/x-pack/test/fleet_functional/apps/fleet/overview_page.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { overviewPage } = getPageObjects(['overviewPage']); - - describe('When in the Fleet application', function () { - this.tags(['ciGroup7']); - - describe('and on the Overview page', () => { - before(async () => { - await overviewPage.navigateToOverview(); - }); - - it('should show the Integrations section', async () => { - await overviewPage.integrationsSectionExistsOrFail(); - }); - - it('should show the Agents section', async () => { - await overviewPage.agentSectionExistsOrFail(); - }); - - it('should show the Agent policies section', async () => { - await overviewPage.agentPolicySectionExistsOrFail(); - }); - - it('should show the Data streams section', async () => { - await overviewPage.datastreamSectionExistsOrFail(); - }); - }); - }); -} diff --git a/x-pack/test/fleet_functional/page_objects/overview_page.ts b/x-pack/test/fleet_functional/page_objects/agents_page.ts similarity index 55% rename from x-pack/test/fleet_functional/page_objects/overview_page.ts rename to x-pack/test/fleet_functional/page_objects/agents_page.ts index 2fd351184c7fe9..99e9ebfdcc15a5 100644 --- a/x-pack/test/fleet_functional/page_objects/overview_page.ts +++ b/x-pack/test/fleet_functional/page_objects/agents_page.ts @@ -11,31 +11,32 @@ import { PLUGIN_ID } from '../../../plugins/fleet/common'; // NOTE: import path below should be the deep path to the actual module - else we get CI errors import { pagePathGetters } from '../../../plugins/fleet/public/constants/page_paths'; -export function OverviewPage({ getService, getPageObjects }: FtrProviderContext) { +export function AgentsPage({ getService, getPageObjects }: FtrProviderContext) { const pageObjects = getPageObjects(['common']); const testSubjects = getService('testSubjects'); return { - async navigateToOverview() { + async navigateToAgentsPage() { await pageObjects.common.navigateToApp(PLUGIN_ID, { - hash: pagePathGetters.overview()[1], + // Fleet's "/" route should redirect to "/agents" + hash: pagePathGetters.base()[1], }); }, - async integrationsSectionExistsOrFail() { - await testSubjects.existOrFail('fleet-integrations-section'); + async agentsTabExistsOrFail() { + await testSubjects.existOrFail('fleet-agents-tab'); }, - async agentPolicySectionExistsOrFail() { - await testSubjects.existOrFail('fleet-agent-policy-section'); + async agentPoliciesTabExistsOrFail() { + await testSubjects.existOrFail('fleet-agent-policies-tab'); }, - async agentSectionExistsOrFail() { - await testSubjects.existOrFail('fleet-agent-section'); + async enrollmentTokensTabExistsOrFail() { + await testSubjects.existOrFail('fleet-enrollment-tokens-tab'); }, - async datastreamSectionExistsOrFail() { - await testSubjects.existOrFail('fleet-datastream-section'); + async dataStreamsTabExistsOrFail() { + await testSubjects.existOrFail('fleet-datastreams-tab'); }, }; } diff --git a/x-pack/test/fleet_functional/page_objects/index.ts b/x-pack/test/fleet_functional/page_objects/index.ts index 2c534285146e54..f0543aa3c7e89e 100644 --- a/x-pack/test/fleet_functional/page_objects/index.ts +++ b/x-pack/test/fleet_functional/page_objects/index.ts @@ -6,9 +6,9 @@ */ import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects'; -import { OverviewPage } from './overview_page'; +import { AgentsPage } from './agents_page'; export const pageObjects = { ...xpackFunctionalPageObjects, - overviewPage: OverviewPage, + agentsPage: AgentsPage, }; From c26d178937d9283691c9566a0c15740cee4bccbf Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 16 Jun 2021 10:32:48 -0700 Subject: [PATCH 13/15] [Reporting] remove unused reference to path.data config (#102267) --- x-pack/plugins/reporting/server/config/config.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugins/reporting/server/config/config.ts b/x-pack/plugins/reporting/server/config/config.ts index 69eafba994b74f..cd4dbd7c19956c 100644 --- a/x-pack/plugins/reporting/server/config/config.ts +++ b/x-pack/plugins/reporting/server/config/config.ts @@ -6,8 +6,7 @@ */ import { get } from 'lodash'; -import { Observable } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { first } from 'rxjs/operators'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { LevelLogger } from '../lib'; import { createConfig$ } from './create_config'; @@ -43,7 +42,6 @@ interface Config { } interface KbnServerConfigType { - path: { data: Observable }; server: { basePath: string; host: string; @@ -68,9 +66,6 @@ export const buildConfig = async ( const serverInfo = http.getServerInfo(); const kbnConfig = { - path: { - data: initContext.config.legacy.globalConfig$.pipe(map((c) => c.path.data)), - }, server: { basePath: core.http.basePath.serverBasePath, host: serverInfo.hostname, From 154150732d3a0c6dd74f50a69e861baeeb551c5b Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 16 Jun 2021 19:42:44 +0200 Subject: [PATCH 14/15] [ML] Functional tests - fix and re-activate alerting flyout test (#102368) This PR fixes the ML alerting flyout tests and re-activates it. --- x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts index 777e6fd598f454..ba7243efe1773f 100644 --- a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts @@ -67,8 +67,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { let testJobId = ''; - // Failing: See https://github.com/elastic/kibana/issues/102012 - describe.skip('anomaly detection alert', function () { + describe('anomaly detection alert', function () { this.tags('ciGroup13'); before(async () => { @@ -119,11 +118,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await ml.testExecution.logTestStep('should preview the alert condition'); await ml.alerting.assertPreviewButtonState(false); - await ml.alerting.setTestInterval('2y'); + await ml.alerting.setTestInterval('5y'); await ml.alerting.assertPreviewButtonState(true); // don't check the exact number provided by the backend, just make sure it's > 0 - await ml.alerting.checkPreview(/Found [1-9]\d* anomalies in the last 2y/); + await ml.alerting.checkPreview(/Found [1-9]\d* anomal(y|ies) in the last 5y/); await ml.testExecution.logTestStep('should create an alert'); await pageObjects.triggersActionsUI.setAlertName('ml-test-alert'); From 8eea4914126fc079cfb77dce29e4ec1899c64807 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 16 Jun 2021 20:15:59 +0200 Subject: [PATCH 15/15] [RAC] Update alert documents in lifecycle rule type helper (#101598) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/services/get_service_alerts.ts | 6 +- .../server/lib/rules/get_top_alerts.ts | 12 +- x-pack/plugins/rule_registry/README.md | 3 - .../create_rule_data_client_mock.ts | 42 ++ .../server/rule_data_client/index.ts | 37 +- .../utils/create_lifecycle_rule_type.test.ts | 381 ++++++++++++++++++ .../create_lifecycle_rule_type_factory.ts | 29 +- .../tests/alerts/rule_registry.ts | 23 +- 8 files changed, 501 insertions(+), 32 deletions(-) create mode 100644 x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts create mode 100644 x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts diff --git a/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts b/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts index f58452ce4d9160..2141570f521c01 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ALERT_UUID } from '@kbn/rule-data-utils/target/technical_field_names'; +import { EVENT_KIND } from '@kbn/rule-data-utils/target/technical_field_names'; import { RuleDataClient } from '../../../../rule_registry/server'; import { SERVICE_NAME, @@ -36,6 +36,7 @@ export async function getServiceAlerts({ ...rangeQuery(start, end), ...environmentQuery(environment), { term: { [SERVICE_NAME]: serviceName } }, + { term: { [EVENT_KIND]: 'signal' } }, ], should: [ { @@ -64,9 +65,6 @@ export async function getServiceAlerts({ }, size: 100, fields: ['*'], - collapse: { - field: ALERT_UUID, - }, sort: { '@timestamp': 'desc', }, diff --git a/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts b/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts index 9560de6ec00ff0..db8191136686a1 100644 --- a/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts +++ b/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ALERT_UUID, TIMESTAMP } from '@kbn/rule-data-utils/target/technical_field_names'; +import { EVENT_KIND, TIMESTAMP } from '@kbn/rule-data-utils/target/technical_field_names'; import { RuleDataClient } from '../../../../rule_registry/server'; import type { AlertStatus } from '../../../common/typings'; import { kqlQuery, rangeQuery, alertStatusQuery } from '../../utils/queries'; @@ -28,13 +28,15 @@ export async function getTopAlerts({ body: { query: { bool: { - filter: [...rangeQuery(start, end), ...kqlQuery(kuery), ...alertStatusQuery(status)], + filter: [ + ...rangeQuery(start, end), + ...kqlQuery(kuery), + ...alertStatusQuery(status), + { term: { [EVENT_KIND]: 'signal' } }, + ], }, }, fields: ['*'], - collapse: { - field: ALERT_UUID, - }, size, sort: { [TIMESTAMP]: 'desc', diff --git a/x-pack/plugins/rule_registry/README.md b/x-pack/plugins/rule_registry/README.md index e12c2b29ed3738..3fe6305a0d9f6e 100644 --- a/x-pack/plugins/rule_registry/README.md +++ b/x-pack/plugins/rule_registry/README.md @@ -111,9 +111,6 @@ const response = await ruleDataClient.getReader().search({ }, size: 100, fields: ['*'], - collapse: { - field: ALERT_UUID, - }, sort: { '@timestamp': 'desc', }, diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts b/x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts new file mode 100644 index 00000000000000..18f3c21fafc155 --- /dev/null +++ b/x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Assign } from '@kbn/utility-types'; +import type { RuleDataClient } from '.'; +import { RuleDataReader, RuleDataWriter } from './types'; + +type MockInstances> = { + [K in keyof T]: T[K] extends (...args: infer TArgs) => infer TReturn + ? jest.MockInstance + : never; +}; + +export function createRuleDataClientMock() { + const bulk = jest.fn(); + const search = jest.fn(); + const getDynamicIndexPattern = jest.fn(); + + return ({ + createOrUpdateWriteTarget: jest.fn(({ namespace }) => Promise.resolve()), + getReader: jest.fn(() => ({ + getDynamicIndexPattern, + search, + })), + getWriter: jest.fn(() => ({ + bulk, + })), + } as unknown) as Assign< + RuleDataClient & Omit, 'options' | 'getClusterClient'>, + { + getWriter: ( + ...args: Parameters + ) => MockInstances; + getReader: ( + ...args: Parameters + ) => MockInstances; + } + >; +} diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/index.ts b/x-pack/plugins/rule_registry/server/rule_data_client/index.ts index cd7467c903e52b..cb336580ca3540 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/index.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/index.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import { isEmpty } from 'lodash'; import type { estypes } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { IndexPatternsFetcher } from '../../../../../src/plugins/data/server'; @@ -44,15 +46,26 @@ export class RuleDataClient implements IRuleDataClient { const clusterClient = await this.getClusterClient(); const indexPatternsFetcher = new IndexPatternsFetcher(clusterClient); - const fields = await indexPatternsFetcher.getFieldsForWildcard({ - pattern: index, - }); - - return { - fields, - timeFieldName: '@timestamp', - title: index, - }; + try { + const fields = await indexPatternsFetcher.getFieldsForWildcard({ + pattern: index, + }); + + return { + fields, + timeFieldName: '@timestamp', + title: index, + }; + } catch (err) { + if (err.output?.payload?.code === 'no_matching_indices') { + return { + fields: [], + timeFieldName: '@timestamp', + title: index, + }; + } + throw err; + } }, }; } @@ -127,6 +140,12 @@ export class RuleDataClient implements IRuleDataClient { const mappings: estypes.MappingTypeMapping = simulateResponse.template.mappings; + if (isEmpty(mappings)) { + throw new Error( + 'No mappings would be generated for this index, possibly due to failed/misconfigured bootstrapping' + ); + } + await clusterClient.indices.putMapping({ index: `${alias}*`, body: mappings }); } } diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts new file mode 100644 index 00000000000000..85e69eb51fd02f --- /dev/null +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -0,0 +1,381 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { loggerMock } from '@kbn/logging/target/mocks'; +import { castArray, omit, mapValues } from 'lodash'; +import { RuleDataClient } from '../rule_data_client'; +import { createRuleDataClientMock } from '../rule_data_client/create_rule_data_client_mock'; +import { createLifecycleRuleTypeFactory } from './create_lifecycle_rule_type_factory'; + +type RuleTestHelpers = ReturnType; + +function createRule() { + const ruleDataClientMock = createRuleDataClientMock(); + + const factory = createLifecycleRuleTypeFactory({ + ruleDataClient: (ruleDataClientMock as unknown) as RuleDataClient, + logger: loggerMock.create(), + }); + + let nextAlerts: Array<{ id: string; fields: Record }> = []; + + const type = factory({ + actionGroups: [ + { + id: 'warning', + name: 'warning', + }, + ], + defaultActionGroupId: 'warning', + executor: async ({ services }) => { + nextAlerts.forEach((alert) => { + services.alertWithLifecycle(alert); + }); + nextAlerts = []; + }, + id: 'test_type', + minimumLicenseRequired: 'basic', + name: 'Test type', + producer: 'test', + actionVariables: { + context: [], + params: [], + state: [], + }, + validate: { + params: schema.object({}, { unknowns: 'allow' }), + }, + }); + + let state: Record = {}; + let previousStartedAt: Date | null; + const createdAt = new Date('2021-06-16T09:00:00.000Z'); + + const scheduleActions = jest.fn(); + + const alertInstanceFactory = () => { + return { + scheduleActions, + } as any; + }; + + return { + alertWithLifecycle: async (alerts: Array<{ id: string; fields: Record }>) => { + nextAlerts = alerts; + + const startedAt = new Date((previousStartedAt ?? createdAt).getTime() + 60000); + + scheduleActions.mockClear(); + + state = await type.executor({ + alertId: 'alertId', + createdBy: 'createdBy', + name: 'name', + params: {}, + previousStartedAt, + startedAt, + rule: { + actions: [], + consumer: 'consumer', + createdAt, + createdBy: 'createdBy', + enabled: true, + name: 'name', + notifyWhen: 'onActionGroupChange', + producer: 'producer', + ruleTypeId: 'ruleTypeId', + ruleTypeName: 'ruleTypeName', + schedule: { + interval: '1m', + }, + tags: ['tags'], + throttle: null, + updatedAt: createdAt, + updatedBy: 'updatedBy', + }, + services: { + alertInstanceFactory, + savedObjectsClient: {} as any, + scopedClusterClient: {} as any, + }, + spaceId: 'spaceId', + state, + tags: ['tags'], + updatedBy: 'updatedBy', + namespace: 'namespace', + }); + + previousStartedAt = startedAt; + }, + scheduleActions, + ruleDataClientMock, + }; +} + +describe('createLifecycleRuleTypeFactory', () => { + describe('with a new rule', () => { + let helpers: RuleTestHelpers; + + beforeEach(() => { + helpers = createRule(); + }); + + describe('when alerts are new', () => { + beforeEach(async () => { + await helpers.alertWithLifecycle([ + { + id: 'opbeans-java', + fields: { + 'service.name': 'opbeans-java', + }, + }, + { + id: 'opbeans-node', + fields: { + 'service.name': 'opbeans-node', + }, + }, + ]); + }); + + it('writes the correct alerts', () => { + expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(1); + + const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[0][0].body!; + + const documents = body.filter((op: any) => !('index' in op)) as any[]; + + const evaluationDocuments = documents.filter((doc) => doc['event.kind'] === 'event'); + const alertDocuments = documents.filter((doc) => doc['event.kind'] === 'signal'); + + expect(evaluationDocuments.length).toBe(2); + expect(alertDocuments.length).toBe(2); + + expect( + alertDocuments.every((doc) => doc['kibana.rac.alert.status'] === 'open') + ).toBeTruthy(); + + expect( + alertDocuments.every((doc) => doc['kibana.rac.alert.duration.us'] === 0) + ).toBeTruthy(); + + expect(alertDocuments.every((doc) => doc['event.action'] === 'open')).toBeTruthy(); + + expect(documents.map((doc) => omit(doc, 'kibana.rac.alert.uuid'))).toMatchInlineSnapshot(` + Array [ + Object { + "@timestamp": "2021-06-16T09:01:00.000Z", + "event.action": "open", + "event.kind": "event", + "kibana.rac.alert.duration.us": 0, + "kibana.rac.alert.id": "opbeans-java", + "kibana.rac.alert.producer": "test", + "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z", + "kibana.rac.alert.status": "open", + "rule.category": "Test type", + "rule.id": "test_type", + "rule.name": "name", + "rule.uuid": "alertId", + "service.name": "opbeans-java", + "tags": Array [ + "tags", + ], + }, + Object { + "@timestamp": "2021-06-16T09:01:00.000Z", + "event.action": "open", + "event.kind": "event", + "kibana.rac.alert.duration.us": 0, + "kibana.rac.alert.id": "opbeans-node", + "kibana.rac.alert.producer": "test", + "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z", + "kibana.rac.alert.status": "open", + "rule.category": "Test type", + "rule.id": "test_type", + "rule.name": "name", + "rule.uuid": "alertId", + "service.name": "opbeans-node", + "tags": Array [ + "tags", + ], + }, + Object { + "@timestamp": "2021-06-16T09:01:00.000Z", + "event.action": "open", + "event.kind": "signal", + "kibana.rac.alert.duration.us": 0, + "kibana.rac.alert.id": "opbeans-java", + "kibana.rac.alert.producer": "test", + "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z", + "kibana.rac.alert.status": "open", + "rule.category": "Test type", + "rule.id": "test_type", + "rule.name": "name", + "rule.uuid": "alertId", + "service.name": "opbeans-java", + "tags": Array [ + "tags", + ], + }, + Object { + "@timestamp": "2021-06-16T09:01:00.000Z", + "event.action": "open", + "event.kind": "signal", + "kibana.rac.alert.duration.us": 0, + "kibana.rac.alert.id": "opbeans-node", + "kibana.rac.alert.producer": "test", + "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z", + "kibana.rac.alert.status": "open", + "rule.category": "Test type", + "rule.id": "test_type", + "rule.name": "name", + "rule.uuid": "alertId", + "service.name": "opbeans-node", + "tags": Array [ + "tags", + ], + }, + ] + `); + }); + }); + + describe('when alerts are active', () => { + beforeEach(async () => { + await helpers.alertWithLifecycle([ + { + id: 'opbeans-java', + fields: { + 'service.name': 'opbeans-java', + }, + }, + { + id: 'opbeans-node', + fields: { + 'service.name': 'opbeans-node', + }, + }, + ]); + + await helpers.alertWithLifecycle([ + { + id: 'opbeans-java', + fields: { + 'service.name': 'opbeans-java', + }, + }, + { + id: 'opbeans-node', + fields: { + 'service.name': 'opbeans-node', + }, + }, + ]); + }); + + it('writes the correct alerts', () => { + expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(2); + + const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[1][0].body!; + + const documents = body.filter((op: any) => !('index' in op)) as any[]; + + const evaluationDocuments = documents.filter((doc) => doc['event.kind'] === 'event'); + const alertDocuments = documents.filter((doc) => doc['event.kind'] === 'signal'); + + expect(evaluationDocuments.length).toBe(2); + expect(alertDocuments.length).toBe(2); + + expect( + alertDocuments.every((doc) => doc['kibana.rac.alert.status'] === 'open') + ).toBeTruthy(); + expect(alertDocuments.every((doc) => doc['event.action'] === 'active')).toBeTruthy(); + + expect(alertDocuments.every((doc) => doc['kibana.rac.alert.duration.us'] > 0)).toBeTruthy(); + }); + }); + + describe('when alerts recover', () => { + beforeEach(async () => { + await helpers.alertWithLifecycle([ + { + id: 'opbeans-java', + fields: { + 'service.name': 'opbeans-java', + }, + }, + { + id: 'opbeans-node', + fields: { + 'service.name': 'opbeans-node', + }, + }, + ]); + + const lastOpbeansNodeDoc = helpers.ruleDataClientMock + .getWriter() + .bulk.mock.calls[0][0].body?.concat() + .reverse() + .find( + (doc: any) => !('index' in doc) && doc['service.name'] === 'opbeans-node' + ) as Record; + + const stored = mapValues(lastOpbeansNodeDoc, (val) => { + return castArray(val); + }); + + helpers.ruleDataClientMock.getReader().search.mockResolvedValueOnce({ + hits: { + hits: [{ fields: stored } as any], + total: { + value: 1, + relation: 'eq', + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 1, + total: 1, + }, + }); + + await helpers.alertWithLifecycle([ + { + id: 'opbeans-java', + fields: { + 'service.name': 'opbeans-java', + }, + }, + ]); + }); + + it('writes the correct alerts', () => { + expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(2); + + const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[1][0].body!; + + const documents = body.filter((op: any) => !('index' in op)) as any[]; + + const opbeansJavaAlertDoc = documents.find( + (doc) => castArray(doc['service.name'])[0] === 'opbeans-java' + ); + const opbeansNodeAlertDoc = documents.find( + (doc) => castArray(doc['service.name'])[0] === 'opbeans-node' + ); + + expect(opbeansJavaAlertDoc['event.action']).toBe('active'); + expect(opbeansJavaAlertDoc['kibana.rac.alert.status']).toBe('open'); + + expect(opbeansNodeAlertDoc['event.action']).toBe('close'); + expect(opbeansNodeAlertDoc['kibana.rac.alert.status']).toBe('closed'); + }); + }); + }); +}); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts index b523dd6770b9f3..c2e0ae7c151ca0 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts @@ -32,7 +32,7 @@ import { AlertTypeWithExecutor } from '../types'; import { ParsedTechnicalFields, parseTechnicalFields } from '../../common/parse_technical_fields'; import { getRuleExecutorData } from './get_rule_executor_data'; -type LifecycleAlertService> = (alert: { +export type LifecycleAlertService> = (alert: { id: string; fields: Record; }) => AlertInstance; @@ -179,7 +179,7 @@ export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({ ...alertData, ...ruleExecutorData, [TIMESTAMP]: timestamp, - [EVENT_KIND]: 'state', + [EVENT_KIND]: 'event', [ALERT_ID]: alertId, }; @@ -221,8 +221,29 @@ export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({ }); if (eventsToIndex.length) { + const alertEvents: Map = new Map(); + + for (const event of eventsToIndex) { + const uuid = event[ALERT_UUID]!; + let storedEvent = alertEvents.get(uuid); + if (!storedEvent) { + storedEvent = event; + } + alertEvents.set(uuid, { + ...storedEvent, + [EVENT_KIND]: 'signal', + }); + } + await ruleDataClient.getWriter().bulk({ - body: eventsToIndex.flatMap((event) => [{ index: {} }, event]), + body: eventsToIndex + .flatMap((event) => [{ index: {} }, event]) + .concat( + Array.from(alertEvents.values()).flatMap((event) => [ + { index: { _id: event[ALERT_UUID]! } }, + event, + ]) + ), }); } @@ -238,7 +259,7 @@ export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({ ); return { - wrapped: nextWrappedState, + wrapped: nextWrappedState ?? {}, trackedAlerts: nextTrackedAlerts, }; }, diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts index 1f8d1144349dd5..3c2e98cfdae47c 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { merge, omit } from 'lodash'; import { format } from 'url'; +import { EVENT_KIND } from '@kbn/rule-data-utils/target/technical_field_names'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; @@ -259,7 +260,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { index: ALERTS_INDEX_TARGET, body: { query: { - match_all: {}, + term: { + [EVENT_KIND]: 'signal', + }, }, size: 1, sort: { @@ -286,7 +289,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { index: ALERTS_INDEX_TARGET, body: { query: { - match_all: {}, + term: { + [EVENT_KIND]: 'signal', + }, }, size: 1, sort: { @@ -313,7 +318,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { index: ALERTS_INDEX_TARGET, body: { query: { - match_all: {}, + term: { + [EVENT_KIND]: 'signal', + }, }, size: 1, sort: { @@ -346,7 +353,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { "open", ], "event.kind": Array [ - "state", + "signal", ], "kibana.rac.alert.duration.us": Array [ 0, @@ -416,7 +423,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { "open", ], "event.kind": Array [ - "state", + "signal", ], "kibana.rac.alert.duration.us": Array [ 0, @@ -486,7 +493,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { index: ALERTS_INDEX_TARGET, body: { query: { - match_all: {}, + term: { + [EVENT_KIND]: 'signal', + }, }, size: 1, sort: { @@ -521,7 +530,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { "close", ], "event.kind": Array [ - "state", + "signal", ], "kibana.rac.alert.evaluation.threshold": Array [ 30,