diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc
index 8623b8c3ca107..3c2a120d167d9 100644
--- a/docs/user/dashboard/lens.asciidoc
+++ b/docs/user/dashboard/lens.asciidoc
@@ -96,13 +96,37 @@ All columns that belong to the same layer pane group are sorted in the table.
* *Text alignment* — Aligns the values in the cell to the *Left*, *Center*, or *Right*.
+* *Color by value* — Applies color to the cell or text values. To change the color, click the *Edit colors* icon.
+
* *Hide column* — Hides the column for the field.
* *Directly filter on click* — Turns column values into clickable links that allow you to filter or drill down into the data.
* *Summary row* — Adds a row that displays the summary value. When specified, allows you to enter a *Summary label*.
-* *Color by value* — Applies color to the cell or text values. To change the color, click *Edit*.
+[float]
+[[assign-colors-to-terms]]
+===== Assign colors to terms
+
+preview::[]
+
+For term-based metrics, assign a color to each term with color mapping.
+
+. Create a custom table.
+
+. In the layer pane, select a *Rows* or *Metrics* field.
+
+. In the *Color by value* option, select *Cell* or *Text*.
+
+. Click the *Edit colors* icon.
+
+. Toggle the button to use the Color Mapping feature.
+
+. Select a color palette and mode.
+
+. Click *Add assignment* to assign a color to a specific term, or click *Add all unassigned terms* to assign colors to all terms. Assigning colors to dates is unsupported.
+
+. Configure color assignments. You can also select whether unassigned terms should be mapped to the selected color palette or a single color.
[float]
[[drag-and-drop-keyboard-navigation]]
diff --git a/package.json b/package.json
index afda7cd4c9125..0ad1d96d7b5db 100644
--- a/package.json
+++ b/package.json
@@ -116,7 +116,7 @@
"@elastic/datemath": "5.0.3",
"@elastic/ebt": "^1.1.1",
"@elastic/ecs": "^8.11.1",
- "@elastic/elasticsearch": "^8.15.0",
+ "@elastic/elasticsearch": "^8.15.1",
"@elastic/ems-client": "8.5.3",
"@elastic/eui": "97.3.0",
"@elastic/filesaver": "1.1.2",
@@ -1498,7 +1498,7 @@
"@octokit/rest": "^17.11.2",
"@parcel/watcher": "^2.1.0",
"@playwright/test": "=1.46.0",
- "@redocly/cli": "^1.25.10",
+ "@redocly/cli": "^1.25.11",
"@statoscope/webpack-plugin": "^5.28.2",
"@storybook/addon-a11y": "^6.5.16",
"@storybook/addon-actions": "^6.5.16",
diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts
index 1aeabb7e86dea..c84b30cf15774 100644
--- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts
+++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts
@@ -72,24 +72,25 @@ describe('catchRetryableEsClientErrors', () => {
type: 'retryable_es_client_error',
});
});
- it('ResponseError with retryable status code', async () => {
- const statusCodes = [503, 401, 403, 408, 410, 429];
- return Promise.all(
- statusCodes.map(async (status) => {
- const error = new esErrors.ResponseError(
- elasticsearchClientMock.createApiResponse({
- statusCode: status,
- body: { error: { type: 'reason' } },
- })
- );
- expect(
- ((await Promise.reject(error).catch(catchRetryableEsClientErrors)) as any).left
- ).toMatchObject({
- message: 'reason',
- type: 'retryable_es_client_error',
- });
- })
- );
- });
+ it.each([503, 401, 403, 408, 410, 429])(
+ 'ResponseError with retryable status code (%d)',
+ async (status) => {
+ const error = new esErrors.ResponseError(
+ elasticsearchClientMock.createApiResponse({
+ statusCode: status,
+ body: { error: { type: 'reason' } },
+ })
+ );
+ expect(
+ ((await Promise.reject(error).catch(catchRetryableEsClientErrors)) as any).left
+ ).toMatchObject({
+ message:
+ status === 410
+ ? 'This API is unavailable in the version of Elasticsearch you are using.'
+ : 'reason',
+ type: 'retryable_es_client_error',
+ });
+ }
+ );
});
});
diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts
index 9848bb0c3d42e..52f7bb201388e 100644
--- a/packages/deeplinks/search/constants.ts
+++ b/packages/deeplinks/search/constants.ts
@@ -25,3 +25,4 @@ export const SEARCH_ELASTICSEARCH = 'enterpriseSearchElasticsearch';
export const SEARCH_VECTOR_SEARCH = 'enterpriseSearchVectorSearch';
export const SEARCH_SEMANTIC_SEARCH = 'enterpriseSearchSemanticSearch';
export const SEARCH_AI_SEARCH = 'enterpriseSearchAISearch';
+export const SEARCH_INDICES_CREATE_INDEX = 'createIndex';
diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts
index 22dfb91bdff33..b23a86b3cc51c 100644
--- a/packages/deeplinks/search/deep_links.ts
+++ b/packages/deeplinks/search/deep_links.ts
@@ -22,6 +22,7 @@ import {
SEARCH_HOMEPAGE,
SEARCH_INDICES_START,
SEARCH_INDICES,
+ SEARCH_INDICES_CREATE_INDEX,
SEARCH_ELASTICSEARCH,
SEARCH_VECTOR_SEARCH,
SEARCH_SEMANTIC_SEARCH,
@@ -55,6 +56,8 @@ export type AppsearchLinkId = 'engines';
export type RelevanceLinkId = 'inferenceEndpoints';
+export type SearchIndicesLinkId = typeof SEARCH_INDICES_CREATE_INDEX;
+
export type DeepLinkId =
| EnterpriseSearchApp
| EnterpriseSearchContentApp
@@ -77,4 +80,5 @@ export type DeepLinkId =
| SearchElasticsearch
| SearchVectorSearch
| SearchSemanticSearch
- | SearchAISearch;
+ | SearchAISearch
+ | `${SearchIndices}:${SearchIndicesLinkId}`;
diff --git a/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts b/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts
index ed6d1b813184b..7fd639331af80 100644
--- a/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts
+++ b/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts
@@ -55,7 +55,6 @@ export class SynthtraceEsClient {
await this.client.indices.resolveIndex({
name: this.indices.join(','),
expand_wildcards: ['open', 'hidden'],
- // @ts-expect-error ignore_unavailable is not in the type definition, but it is accepted by es
ignore_unavailable: true,
})
).indices.map((index: { name: string }) => index.name)
diff --git a/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts b/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts
index 9e21c45f75b4b..c8880b9bfe678 100644
--- a/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts
+++ b/packages/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts
@@ -78,15 +78,6 @@ describe('single line query', () => {
});
});
- describe('SHOW', () => {
- /** @todo Enable once show command args are parsed as columns. */
- test.skip('info page', () => {
- const { text } = reprint('SHOW info');
-
- expect(text).toBe('SHOW info');
- });
- });
-
describe('STATS', () => {
test('with aggregates assignment', () => {
const { text } = reprint('FROM a | STATS var = agg(123, fn(true))');
@@ -100,6 +91,30 @@ describe('single line query', () => {
expect(text).toBe('FROM a | STATS A(1), B(2) BY asdf');
});
});
+
+ describe('GROK', () => {
+ test('two basic arguments', () => {
+ const { text } = reprint('FROM search-movies | GROK Awards "text"');
+
+ expect(text).toBe('FROM search-movies | GROK Awards "text"');
+ });
+ });
+
+ describe('DISSECT', () => {
+ test('two basic arguments', () => {
+ const { text } = reprint('FROM index | DISSECT input "pattern"');
+
+ expect(text).toBe('FROM index | DISSECT input "pattern"');
+ });
+
+ test('with APPEND_SEPARATOR option', () => {
+ const { text } = reprint(
+ 'FROM index | DISSECT input "pattern" APPEND_SEPARATOR=""'
+ );
+
+ expect(text).toBe('FROM index | DISSECT input "pattern" APPEND_SEPARATOR = ""');
+ });
+ });
});
describe('expressions', () => {
diff --git a/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts b/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts
index 2dfe239ce5b88..6422ae9a451af 100644
--- a/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts
+++ b/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts
@@ -19,6 +19,83 @@ const reprint = (src: string, opts?: WrappingPrettyPrinterOptions) => {
return { text };
};
+describe('commands', () => {
+ describe('GROK', () => {
+ test('two basic arguments', () => {
+ const { text } = reprint('FROM search-movies | GROK Awards "text"');
+
+ expect(text).toBe('FROM search-movies | GROK Awards "text"');
+ });
+
+ test('two long arguments', () => {
+ const { text } = reprint(
+ 'FROM search-movies | GROK AwardsAwardsAwardsAwardsAwardsAwardsAwardsAwards "texttexttexttexttexttexttexttexttexttexttexttexttexttexttext"'
+ );
+
+ expect('\n' + text).toBe(`
+FROM search-movies
+ | GROK
+ AwardsAwardsAwardsAwardsAwardsAwardsAwardsAwards
+ "texttexttexttexttexttexttexttexttexttexttexttexttexttexttext"`);
+ });
+ });
+
+ describe('DISSECT', () => {
+ test('two basic arguments', () => {
+ const { text } = reprint('FROM index | DISSECT input "pattern"');
+
+ expect(text).toBe('FROM index | DISSECT input "pattern"');
+ });
+
+ test('two long arguments', () => {
+ const { text } = reprint(
+ 'FROM index | DISSECT InputInputInputInputInputInputInputInputInputInputInputInputInputInput "PatternPatternPatternPatternPatternPatternPatternPatternPatternPattern"'
+ );
+
+ expect('\n' + text).toBe(`
+FROM index
+ | DISSECT
+ InputInputInputInputInputInputInputInputInputInputInputInputInputInput
+ "PatternPatternPatternPatternPatternPatternPatternPatternPatternPattern"`);
+ });
+
+ test('with APPEND_SEPARATOR option', () => {
+ const { text } = reprint(
+ 'FROM index | DISSECT input "pattern" APPEND_SEPARATOR=""'
+ );
+
+ expect(text).toBe('FROM index | DISSECT input "pattern" APPEND_SEPARATOR = ""');
+ });
+
+ test('two long arguments with short APPEND_SEPARATOR option', () => {
+ const { text } = reprint(
+ 'FROM index | DISSECT InputInputInputInputInputInputInputInputInputInputInputInputInputInput "PatternPatternPatternPatternPatternPatternPatternPatternPatternPattern" APPEND_SEPARATOR="sep"'
+ );
+
+ expect('\n' + text).toBe(`
+FROM index
+ | DISSECT
+ InputInputInputInputInputInputInputInputInputInputInputInputInputInput
+ "PatternPatternPatternPatternPatternPatternPatternPatternPatternPattern"
+ APPEND_SEPARATOR = "sep"`);
+ });
+
+ test('two long arguments with long APPEND_SEPARATOR option', () => {
+ const { text } = reprint(
+ 'FROM index | DISSECT InputInputInputInputInputInputInputInputInputInputInputInputInputInput "PatternPatternPatternPatternPatternPatternPatternPatternPatternPattern" APPEND_SEPARATOR=""'
+ );
+
+ expect('\n' + text).toBe(`
+FROM index
+ | DISSECT
+ InputInputInputInputInputInputInputInputInputInputInputInputInputInput
+ "PatternPatternPatternPatternPatternPatternPatternPatternPatternPattern"
+ APPEND_SEPARATOR =
+ ""`);
+ });
+ });
+});
+
describe('casing', () => {
test('can chose command name casing', () => {
const query = 'FROM index | WHERE a == 123';
diff --git a/packages/kbn-esql-ast/src/pretty_print/basic_pretty_printer.ts b/packages/kbn-esql-ast/src/pretty_print/basic_pretty_printer.ts
index 2f1e3439cd3a3..cf252825c243f 100644
--- a/packages/kbn-esql-ast/src/pretty_print/basic_pretty_printer.ts
+++ b/packages/kbn-esql-ast/src/pretty_print/basic_pretty_printer.ts
@@ -19,6 +19,7 @@ import {
import { ESQLAstBaseItem, ESQLAstCommand, ESQLAstQueryExpression } from '../types';
import { ESQLAstExpressionNode, Visitor } from '../visitor';
import { resolveItem } from '../visitor/utils';
+import { commandOptionsWithEqualsSeparator, commandsWithNoCommaArgSeparator } from './constants';
import { LeafPrinter } from './leaf_printer';
export interface BasicPrettyPrinterOptions {
@@ -378,7 +379,8 @@ export class BasicPrettyPrinter {
args += (args ? ', ' : '') + arg;
}
- const argsFormatted = args ? ` ${args}` : '';
+ const separator = commandOptionsWithEqualsSeparator.has(ctx.node.name) ? ' = ' : ' ';
+ const argsFormatted = args ? `${separator}${args}` : '';
const optionFormatted = `${option}${argsFormatted}`;
return optionFormatted;
@@ -392,7 +394,10 @@ export class BasicPrettyPrinter {
let options = '';
for (const source of ctx.visitArguments()) {
- args += (args ? ', ' : '') + source;
+ const needsSeparator = !!args;
+ const needsComma = !commandsWithNoCommaArgSeparator.has(ctx.node.name);
+ const separator = needsSeparator ? (needsComma ? ',' : '') + ' ' : '';
+ args += separator + source;
}
for (const option of ctx.visitOptions()) {
diff --git a/packages/kbn-esql-ast/src/pretty_print/constants.ts b/packages/kbn-esql-ast/src/pretty_print/constants.ts
new file mode 100644
index 0000000000000..01208af98d025
--- /dev/null
+++ b/packages/kbn-esql-ast/src/pretty_print/constants.ts
@@ -0,0 +1,52 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+/**
+ * This set tracks commands that don't use commas to separate their
+ * arguments.
+ *
+ * Normally ES|QL command arguments are separated by commas.
+ *
+ * ```
+ * COMMAND arg1, arg2, arg3
+ * ```
+ *
+ * But there are some commands (namely `grok` and `dissect`) which don't
+ * use commas to separate their arguments.
+ *
+ * ```
+ * GROK input "pattern"
+ * DISSECT input "pattern"
+ * ```
+ */
+export const commandsWithNoCommaArgSeparator = new Set(['grok', 'dissect']);
+
+/**
+ * This set tracks command options which use an equals sign to separate
+ * the option label from the option value.
+ *
+ * Most ES|QL commands use a space to separate the option label from the
+ * option value.
+ *
+ * ```
+ * COMMAND arg1, arg2, arg3 OPTION option
+ * FROM index METADATA _id
+ * ```
+ *
+ * However, the `APPEND_SEPARATOR` in the `DISSECT` command uses an equals
+ * sign to separate the option label from the option value.
+ *
+ * ```
+ * DISSECT input "pattern" APPEND_SEPARATOR = "separator"
+ * |
+ * |
+ * equals sign
+ * ```
+ */
+export const commandOptionsWithEqualsSeparator = new Set(['append_separator']);
diff --git a/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts b/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts
index 91f65a389f0c3..2f863524740ee 100644
--- a/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts
+++ b/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts
@@ -20,6 +20,7 @@ import {
} from '../visitor';
import { children, singleItems } from '../visitor/utils';
import { BasicPrettyPrinter, BasicPrettyPrinterOptions } from './basic_pretty_printer';
+import { commandOptionsWithEqualsSeparator, commandsWithNoCommaArgSeparator } from './constants';
import { getPrettyPrintStats } from './helpers';
import { LeafPrinter } from './leaf_printer';
@@ -259,6 +260,8 @@ export class WrappingPrettyPrinter {
}
}
+ const commaBetweenArgs = !commandsWithNoCommaArgSeparator.has(ctx.node.name);
+
if (!oneArgumentPerLine) {
ARGS: for (const arg of singleItems(ctx.arguments())) {
if (arg.type === 'option') {
@@ -271,7 +274,8 @@ export class WrappingPrettyPrinter {
if (formattedArgLength > largestArg) {
largestArg = formattedArgLength;
}
- let separator = txt ? ',' : '';
+
+ let separator = txt ? (commaBetweenArgs ? ',' : '') : '';
let fragment = '';
if (needsWrap) {
@@ -329,7 +333,7 @@ export class WrappingPrettyPrinter {
const arg = ctx.visitExpression(args[i], {
indent,
remaining: this.opts.wrap - indent.length,
- suffix: isLastArg ? '' : ',',
+ suffix: isLastArg ? '' : commaBetweenArgs ? ',' : '',
});
const separator = isFirstArg ? '' : '\n';
const indentation = arg.indented ? '' : indent;
@@ -557,8 +561,9 @@ export class WrappingPrettyPrinter {
indent: inp.indent,
remaining: inp.remaining - option.length - 1,
});
- const argsFormatted = args.txt ? ` ${args.txt}` : '';
- const txt = `${option}${argsFormatted}`;
+ const argsFormatted = args.txt ? `${args.txt[0] === '\n' ? '' : ' '}${args.txt}` : '';
+ const separator = commandOptionsWithEqualsSeparator.has(ctx.node.name) ? ' =' : '';
+ const txt = `${option}${separator}${argsFormatted}`;
return { txt, lines: args.lines };
})
diff --git a/src/plugins/controls/common/constants.ts b/src/plugins/controls/common/constants.ts
index d1434d4df2ae0..afd6fe66f0df1 100644
--- a/src/plugins/controls/common/constants.ts
+++ b/src/plugins/controls/common/constants.ts
@@ -16,7 +16,7 @@ export const CONTROL_CHAINING_OPTIONS = { NONE: 'NONE', HIERARCHICAL: 'HIERARCHI
export const DEFAULT_CONTROL_WIDTH: ControlWidth = CONTROL_WIDTH_OPTIONS.MEDIUM;
export const DEFAULT_CONTROL_LABEL_POSITION: ControlLabelPosition =
CONTROL_LABEL_POSITION_OPTIONS.ONE_LINE;
-export const DEFAULT_CONTROL_GROW: boolean = true;
+export const DEFAULT_CONTROL_GROW: boolean = false;
export const DEFAULT_CONTROL_CHAINING: ControlGroupChainingSystem =
CONTROL_CHAINING_OPTIONS.HIERARCHICAL;
export const DEFAULT_IGNORE_PARENT_SETTINGS = {
diff --git a/src/plugins/controls/public/control_group/components/control_group_editor.tsx b/src/plugins/controls/public/control_group/components/control_group_editor.tsx
index 8f1ccb4d699b0..cb21c23bc9ce4 100644
--- a/src/plugins/controls/public/control_group/components/control_group_editor.tsx
+++ b/src/plugins/controls/public/control_group/components/control_group_editor.tsx
@@ -72,7 +72,7 @@ export const ControlGroupEditor = ({ onCancel, onSave, onDeleteAll, stateManager
return (
<>
-
+
{ControlGroupStrings.management.getFlyoutTitle()}
@@ -80,7 +80,7 @@ export const ControlGroupEditor = ({ onCancel, onSave, onDeleteAll, stateManager
{
onCancel();
}}
@@ -204,7 +204,7 @@ export const ControlGroupEditor = ({ onCancel, onSave, onDeleteAll, stateManager
{
diff --git a/src/plugins/controls/public/control_group/control_group_strings.tsx b/src/plugins/controls/public/control_group/control_group_strings.tsx
index b8f6a11abf839..f5c92d987b271 100644
--- a/src/plugins/controls/public/control_group/control_group_strings.tsx
+++ b/src/plugins/controls/public/control_group/control_group_strings.tsx
@@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
export const ControlGroupStrings = {
getSaveChangesTitle: () =>
i18n.translate('controls.controlGroup.manageControl.saveChangesTitle', {
- defaultMessage: 'Save and close',
+ defaultMessage: 'Save',
}),
getCancelTitle: () =>
i18n.translate('controls.controlGroup.manageControl.cancelTitle', {
diff --git a/src/plugins/controls/public/control_group/init_controls_manager.test.ts b/src/plugins/controls/public/control_group/init_controls_manager.test.ts
index 29998325664bb..d88dc5452a0e5 100644
--- a/src/plugins/controls/public/control_group/init_controls_manager.test.ts
+++ b/src/plugins/controls/public/control_group/init_controls_manager.test.ts
@@ -263,7 +263,7 @@ describe('getNewControlState', () => {
test('should contain defaults when there are no existing controls', () => {
const controlsManager = initControlsManager({}, new BehaviorSubject({}));
expect(controlsManager.getNewControlState()).toEqual({
- grow: true,
+ grow: false,
width: 'medium',
dataViewId: undefined,
});
@@ -284,7 +284,7 @@ describe('getNewControlState', () => {
new BehaviorSubject(intialControlsState)
);
expect(controlsManager.getNewControlState()).toEqual({
- grow: true,
+ grow: false,
width: 'medium',
dataViewId: 'myOtherDataViewId',
});
diff --git a/src/plugins/controls/public/control_group/open_edit_control_group_flyout.tsx b/src/plugins/controls/public/control_group/open_edit_control_group_flyout.tsx
index 54e35ab271b34..459913d98de0b 100644
--- a/src/plugins/controls/public/control_group/open_edit_control_group_flyout.tsx
+++ b/src/plugins/controls/public/control_group/open_edit_control_group_flyout.tsx
@@ -101,6 +101,9 @@ export const openEditControlGroupFlyout = (
'aria-label': i18n.translate('controls.controlGroup.manageControl', {
defaultMessage: 'Edit control settings',
}),
+ size: 'm',
+ maxWidth: 500,
+ paddingSize: 'm',
outsideClickCloses: false,
onClose: () => closeOverlay(overlay),
}
diff --git a/src/plugins/controls/public/controls/data_controls/data_control_constants.tsx b/src/plugins/controls/public/controls/data_controls/data_control_constants.tsx
index 6b06bd8a52439..23d4c68f6c5dc 100644
--- a/src/plugins/controls/public/controls/data_controls/data_control_constants.tsx
+++ b/src/plugins/controls/public/controls/data_controls/data_control_constants.tsx
@@ -21,14 +21,6 @@ export const DataControlEditorStrings = {
defaultMessage: 'Edit control',
}),
dataSource: {
- getFormGroupTitle: () =>
- i18n.translate('controls.controlGroup.manageControl.dataSource.formGroupTitle', {
- defaultMessage: 'Data source',
- }),
- getFormGroupDescription: () =>
- i18n.translate('controls.controlGroup.manageControl.dataSource.formGroupDescription', {
- defaultMessage: 'Select the data view and field that you want to create a control for.',
- }),
getSelectDataViewMessage: () =>
i18n.translate('controls.controlGroup.manageControl.dataSource.selectDataViewMessage', {
defaultMessage: 'Please select a data view',
@@ -95,14 +87,6 @@ export const DataControlEditorStrings = {
},
},
displaySettings: {
- getFormGroupTitle: () =>
- i18n.translate('controls.controlGroup.manageControl.displaySettings.formGroupTitle', {
- defaultMessage: 'Display settings',
- }),
- getFormGroupDescription: () =>
- i18n.translate('controls.controlGroup.manageControl.displaySettings.formGroupDescription', {
- defaultMessage: 'Change how the control appears on your dashboard.',
- }),
getTitleInputTitle: () =>
i18n.translate('controls.controlGroup.manageControl.displaySettings.titleInputTitle', {
defaultMessage: 'Label',
@@ -133,7 +117,7 @@ export const DataControlEditorStrings = {
},
getSaveChangesTitle: () =>
i18n.translate('controls.controlGroup.manageControl.saveChangesTitle', {
- defaultMessage: 'Save and close',
+ defaultMessage: 'Save',
}),
getCancelTitle: () =>
i18n.translate('controls.controlGroup.manageControl.cancelTitle', {
diff --git a/src/plugins/controls/public/controls/data_controls/data_control_editor.tsx b/src/plugins/controls/public/controls/data_controls/data_control_editor.tsx
index 23fd95978ff82..a84425f350dc1 100644
--- a/src/plugins/controls/public/controls/data_controls/data_control_editor.tsx
+++ b/src/plugins/controls/public/controls/data_controls/data_control_editor.tsx
@@ -15,7 +15,6 @@ import {
EuiButtonEmpty,
EuiButtonGroup,
EuiCallOut,
- EuiDescribedFormGroup,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
@@ -250,20 +249,8 @@ export const DataControlEditor =
- {DataControlEditorStrings.manageControl.controlTypeSettings.getFormGroupTitle(
- controlFactory.getDisplayName()
- )}
-
- }
- description={DataControlEditorStrings.manageControl.controlTypeSettings.getFormGroupDescription(
- controlFactory.getDisplayName()
- )}
- data-test-subj="control-editor-custom-settings"
- >
+
+
-
+
);
}, [fieldRegistry, controlFactory, initialState, editorState, controlGroupApi]);
return (
<>
-
+
{!controlId // if no ID, then we are creating a new control
? DataControlEditorStrings.manageControl.getFlyoutCreateTitle()
@@ -288,156 +275,144 @@ export const DataControlEditor =
- {DataControlEditorStrings.manageControl.dataSource.getFormGroupTitle()}
}
- description={DataControlEditorStrings.manageControl.dataSource.getFormGroupDescription()}
- >
- {!editorConfig?.hideDataViewSelector && (
-
- {dataViewListError ? (
-
- {dataViewListError.message}
-
- ) : (
- {
- setEditorState({ ...editorState, dataViewId: newDataViewId });
- setSelectedControlType(undefined);
- }}
- trigger={{
- label:
- selectedDataView?.getName() ??
- DataControlEditorStrings.manageControl.dataSource.getSelectDataViewMessage(),
- }}
- selectableProps={{ isLoading: dataViewListLoading }}
- />
- )}
-
- )}
-
-
- {fieldListError ? (
+ {!editorConfig?.hideDataViewSelector && (
+
+ {dataViewListError ? (
- {fieldListError.message}
+ {dataViewListError.message}
) : (
- {
- const customPredicate = editorConfig?.fieldFilterPredicate?.(field) ?? true;
- return Boolean(fieldRegistry?.[field.name]) && customPredicate;
+ {
+ setEditorState({ ...editorState, dataViewId: newDataViewId });
+ setSelectedControlType(undefined);
}}
- selectedFieldName={editorState.fieldName}
- dataView={selectedDataView}
- onSelectField={(field) => {
- setEditorState({ ...editorState, fieldName: field.name });
-
- /**
- * make sure that the new field is compatible with the selected control type and, if it's not,
- * reset the selected control type to the **first** compatible control type
- */
- const newCompatibleControlTypes =
- fieldRegistry?.[field.name]?.compatibleControlTypes ?? [];
- if (
- !selectedControlType ||
- !newCompatibleControlTypes.includes(selectedControlType!)
- ) {
- setSelectedControlType(newCompatibleControlTypes[0]);
- }
-
- /**
- * set the control title (i.e. the one set by the user) + default title (i.e. the field display name)
- */
- const newDefaultTitle = field.displayName ?? field.name;
- setDefaultPanelTitle(newDefaultTitle);
- const currentTitle = editorState.title;
- if (!currentTitle || currentTitle === newDefaultTitle) {
- setPanelTitle(newDefaultTitle);
- }
-
- setControlOptionsValid(true); // reset options state
+ trigger={{
+ label:
+ selectedDataView?.getName() ??
+ DataControlEditorStrings.manageControl.dataSource.getSelectDataViewMessage(),
}}
- selectableProps={{ isLoading: dataViewListLoading || dataViewLoading }}
+ selectableProps={{ isLoading: dataViewListLoading }}
/>
)}
+ )}
+
+
+ {fieldListError ? (
+
+ {fieldListError.message}
+
+ ) : (
+ {
+ const customPredicate = editorConfig?.fieldFilterPredicate?.(field) ?? true;
+ return Boolean(fieldRegistry?.[field.name]) && customPredicate;
+ }}
+ selectedFieldName={editorState.fieldName}
+ dataView={selectedDataView}
+ onSelectField={(field) => {
+ setEditorState({ ...editorState, fieldName: field.name });
+
+ /**
+ * make sure that the new field is compatible with the selected control type and, if it's not,
+ * reset the selected control type to the **first** compatible control type
+ */
+ const newCompatibleControlTypes =
+ fieldRegistry?.[field.name]?.compatibleControlTypes ?? [];
+ if (
+ !selectedControlType ||
+ !newCompatibleControlTypes.includes(selectedControlType!)
+ ) {
+ setSelectedControlType(newCompatibleControlTypes[0]);
+ }
+
+ /**
+ * set the control title (i.e. the one set by the user) + default title (i.e. the field display name)
+ */
+ const newDefaultTitle = field.displayName ?? field.name;
+ setDefaultPanelTitle(newDefaultTitle);
+ const currentTitle = editorState.title;
+ if (!currentTitle || currentTitle === newDefaultTitle) {
+ setPanelTitle(newDefaultTitle);
+ }
+
+ setControlOptionsValid(true); // reset options state
+ }}
+ selectableProps={{ isLoading: dataViewListLoading || dataViewLoading }}
+ />
+ )}
+
+
+ {/* wrapping in `div` so that focus gets passed properly to the form row */}
+
+
+
+
+
+ {
+ setPanelTitle(e.target.value ?? '');
+ setEditorState({
+ ...editorState,
+ title: e.target.value === '' ? undefined : e.target.value,
+ });
+ }}
+ />
+
+ {!editorConfig?.hideWidthSettings && (
- {/* wrapping in `div` so that focus gets passed properly to the form row */}
-
+ setEditorState({ ...editorState, width: newWidth as ControlWidth })
+ }
+ />
+
+ setEditorState({ ...editorState, grow: !editorState.grow })}
+ data-test-subj="control-editor-grow-switch"
/>
-
- {DataControlEditorStrings.manageControl.displaySettings.getFormGroupTitle()}
- }
- description={DataControlEditorStrings.manageControl.displaySettings.getFormGroupDescription()}
- >
-
- {
- setPanelTitle(e.target.value ?? '');
- setEditorState({
- ...editorState,
- title: e.target.value === '' ? undefined : e.target.value,
- });
- }}
- />
-
- {!editorConfig?.hideWidthSettings && (
-
-
-
- setEditorState({ ...editorState, width: newWidth as ControlWidth })
- }
- />
-
- setEditorState({ ...editorState, grow: !editorState.grow })}
- data-test-subj="control-editor-grow-switch"
- />
-
-
- )}
-
+ )}
{!editorConfig?.hideAdditionalSettings && CustomSettingsComponent}
{controlId && (
<>
@@ -464,7 +439,6 @@ export const DataControlEditor = {
onCancel(editorState);
}}
@@ -476,7 +450,7 @@ export const DataControlEditor = closeOverlay(overlay),
}
);
diff --git a/src/plugins/controls/public/controls/data_controls/options_list_control/components/options_list_editor_options.tsx b/src/plugins/controls/public/controls/data_controls/options_list_control/components/options_list_editor_options.tsx
index e9dad12be5623..f07a7cc6c58bf 100644
--- a/src/plugins/controls/public/controls/data_controls/options_list_control/components/options_list_editor_options.tsx
+++ b/src/plugins/controls/public/controls/data_controls/options_list_control/components/options_list_editor_options.tsx
@@ -131,6 +131,7 @@ export const OptionsListEditorOptions = ({
data-test-subj="optionsListControl__selectionOptionsRadioGroup"
>
{
@@ -146,6 +147,7 @@ export const OptionsListEditorOptions = ({
data-test-subj="optionsListControl__searchOptionsRadioGroup"
>
{
@@ -158,6 +160,7 @@ export const OptionsListEditorOptions = ({
)}
{
const newStep = event.target.valueAsNumber;
diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx
index dbb86046def06..e6adece8ab36d 100644
--- a/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx
+++ b/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx
@@ -125,7 +125,7 @@ export const DashboardPanelSelectionListFlyout: React.FC<
return (
<>
-
+
{
@@ -281,7 +282,7 @@ export const DashboardPanelSelectionListFlyout: React.FC<
diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx
index 2cad63c442026..cf7f9c65c6618 100644
--- a/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx
+++ b/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx
@@ -43,7 +43,7 @@ export const EditorMenu = ({ createNewVisType, isDisabled }: EditorMenuProps) =>
function openDashboardPanelSelectionFlyout() {
const flyoutPanelPaddingSize: ComponentProps<
typeof DashboardPanelSelectionListFlyout
- >['paddingSize'] = 'l';
+ >['paddingSize'] = 'm';
const mount = toMountPoint(
React.createElement(function () {
diff --git a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx
index b83ebbcb49d66..b334dbcb5857a 100644
--- a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx
@@ -189,7 +189,7 @@ export const AddPanelFlyout = ({
return (
<>
-
+
{i18n.translate('embeddableApi.addPanel.Title', { defaultMessage: 'Add from library' })}
diff --git a/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx
index 160289d0d1c2a..9ba3c00a73745 100644
--- a/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx
@@ -52,6 +52,9 @@ export const openAddPanelFlyout = ({
if (onClose) onClose();
overlayRef.close();
},
+ size: 'm',
+ maxWidth: 500,
+ paddingSize: 'm',
'data-test-subj': 'dashboardAddPanel',
'aria-labelledby': modalTitleId,
}
diff --git a/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.test.tsx b/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.test.tsx
index 265f162d04f6c..f052f0526a945 100644
--- a/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.test.tsx
+++ b/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.test.tsx
@@ -43,11 +43,11 @@ const ImageEditor = (props: Partial) => {
);
};
-test('should call onCancel when "Close" clicked', async () => {
+test('should call onCancel when "Cancel" clicked', async () => {
const onCancel = jest.fn();
const { getByText } = render();
- expect(getByText('Close')).toBeVisible();
- await userEvent.click(getByText('Close'));
+ expect(getByText('Cancel')).toBeVisible();
+ await userEvent.click(getByText('Cancel'));
expect(onCancel).toBeCalled();
});
diff --git a/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.tsx b/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.tsx
index 2c57f25db6c8b..1a5ee3bc64e1d 100644
--- a/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.tsx
+++ b/src/plugins/image_embeddable/public/components/image_editor/image_editor_flyout.tsx
@@ -121,7 +121,7 @@ export function ImageEditorFlyout(props: ImageEditorFlyoutProps) {
return (
<>
-
+
{isEditing ? (
-
-
+
+
+
setSrcType('file')} isSelected={srcType === 'file'}>
-
-
+
{srcType === 'file' && (
<>
{isDraftImageConfigValid ? (
@@ -238,7 +238,7 @@ export function ImageEditorFlyout(props: ImageEditorFlyoutProps) {
/>
}
- titleSize={'s'}
+ titleSize={'xs'}
/>
) : (
)}
-
-
+
>
)}
-
-
-
-
-
-
+
diff --git a/src/plugins/image_embeddable/public/components/image_editor/open_image_editor.tsx b/src/plugins/image_embeddable/public/components/image_editor/open_image_editor.tsx
index ae8ced88d14ef..f730147cb0d2c 100644
--- a/src/plugins/image_embeddable/public/components/image_editor/open_image_editor.tsx
+++ b/src/plugins/image_embeddable/public/components/image_editor/open_image_editor.tsx
@@ -79,6 +79,9 @@ export const openImageEditor = async ({
onClose: () => {
onCancel();
},
+ size: 'm',
+ maxWidth: 500,
+ paddingSize: 'm',
ownFocus: true,
'data-test-subj': 'createImageEmbeddableFlyout',
}
diff --git a/src/plugins/links/public/components/dashboard_link/dashboard_link_destination_picker.tsx b/src/plugins/links/public/components/dashboard_link/dashboard_link_destination_picker.tsx
index b062b9befa284..ab5b923327049 100644
--- a/src/plugins/links/public/components/dashboard_link/dashboard_link_destination_picker.tsx
+++ b/src/plugins/links/public/components/dashboard_link/dashboard_link_destination_picker.tsx
@@ -119,6 +119,7 @@ export const DashboardLinkDestinationPicker = ({
return (
onClose()}
>
-
+
{link
? LinksStrings.editor.getEditLinkTitle()
@@ -113,6 +113,7 @@ export const LinkEditor = ({
{
@@ -131,6 +132,7 @@ export const LinkEditor = ({
/>
onClose()}
- iconType="cross"
data-test-subj="links--linkEditor--closeBtn"
>
{LinksStrings.editor.getCancelButtonLabel()}
@@ -160,6 +162,7 @@ export const LinkEditor = ({
{
// this check should always be true, since the button is disabled otherwise - this is just for type safety
diff --git a/src/plugins/links/public/components/editor/links_editor.scss b/src/plugins/links/public/components/editor/links_editor.scss
index 02961c7d5f5cb..c33b95350df98 100644
--- a/src/plugins/links/public/components/editor/links_editor.scss
+++ b/src/plugins/links/public/components/editor/links_editor.scss
@@ -3,7 +3,7 @@
.linksPanelEditor {
.linkEditor {
@include euiFlyout;
- max-inline-size: $euiSizeXXL * 18; // 40px * 18 = 720px
+ max-inline-size: $euiSizeXS * 125; // 4px * 125 = 500px
&.in {
animation: euiFlyoutOpenAnimation $euiAnimSpeedNormal $euiAnimSlightResistance;
@@ -59,6 +59,9 @@
}
.links_hoverActions {
+ background-color: $euiColorEmptyShade;
+ position: absolute;
+ right: $euiSizeL;
opacity: 0;
visibility: hidden;
transition: visibility $euiAnimSpeedNormal, opacity $euiAnimSpeedNormal;
diff --git a/src/plugins/links/public/components/editor/links_editor.tsx b/src/plugins/links/public/components/editor/links_editor.tsx
index 93ca47e364c57..8fa33fd4ebcaa 100644
--- a/src/plugins/links/public/components/editor/links_editor.tsx
+++ b/src/plugins/links/public/components/editor/links_editor.tsx
@@ -167,7 +167,7 @@ const LinksEditor = ({
-
+
{isEditingExisting
? LinksStrings.editor.panelEditor.getEditFlyoutTitle()
@@ -251,7 +251,6 @@ const LinksEditor = ({
@@ -268,6 +267,7 @@ const LinksEditor = ({
data-test-subj="links--panelEditor--saveByReferenceTooltip"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/src/plugins/links/public/components/external_link/external_link_destination_picker.tsx b/src/plugins/links/public/components/external_link/external_link_destination_picker.tsx
index dfdc6e0589e6b..5b8522b39960e 100644
--- a/src/plugins/links/public/components/external_link/external_link_destination_picker.tsx
+++ b/src/plugins/links/public/components/external_link/external_link_destination_picker.tsx
@@ -54,6 +54,7 @@ export const ExternalLinkDestinationPicker = ({
return (
i18n.translate('links.editor.cancelButtonLabel', {
- defaultMessage: 'Close',
+ defaultMessage: 'Cancel',
}),
panelEditor: {
getLinksTitle: () =>
diff --git a/src/plugins/links/public/editor/open_editor_flyout.tsx b/src/plugins/links/public/editor/open_editor_flyout.tsx
index 041672e89dbbe..87b1ab4e21ff8 100644
--- a/src/plugins/links/public/editor/open_editor_flyout.tsx
+++ b/src/plugins/links/public/editor/open_editor_flyout.tsx
@@ -137,7 +137,8 @@ export async function openEditorFlyout({
),
{
id: flyoutId,
- maxWidth: 720,
+ maxWidth: 500,
+ paddingSize: 'm',
ownFocus: true,
onClose: onCancel,
outsideClickCloses: false,
diff --git a/src/plugins/presentation_util/public/components/dashboard_drilldown_options/dashboard_drilldown_options.tsx b/src/plugins/presentation_util/public/components/dashboard_drilldown_options/dashboard_drilldown_options.tsx
index 921560f7a2224..63ff89da2ec17 100644
--- a/src/plugins/presentation_util/public/components/dashboard_drilldown_options/dashboard_drilldown_options.tsx
+++ b/src/plugins/presentation_util/public/components/dashboard_drilldown_options/dashboard_drilldown_options.tsx
@@ -8,7 +8,7 @@
*/
import React from 'react';
-import { EuiFormRow, EuiSwitch } from '@elastic/eui';
+import { EuiFormRow, EuiSpacer, EuiSwitch } from '@elastic/eui';
import { DashboardDrilldownOptions } from './types';
import { dashboardDrilldownConfigStrings } from '../../i18n/dashboard_drilldown_config';
@@ -24,32 +24,35 @@ export const DashboardDrilldownOptionsComponent = ({
}: DashboardDrilldownOptionsProps) => {
return (
<>
-
- onOptionChange({ useCurrentFilters: !options.useCurrentFilters })}
- data-test-subj="dashboardDrillDownOptions--useCurrentFilters--checkbox"
- />
-
-
- onOptionChange({ useCurrentDateRange: !options.useCurrentDateRange })}
- data-test-subj="dashboardDrillDownOptions--useCurrentDateRange--checkbox"
- />
-
-
- onOptionChange({ openInNewTab: !options.openInNewTab })}
- data-test-subj="dashboardDrillDownOptions--openInNewTab--checkbox"
- />
+
+
+ onOptionChange({ useCurrentFilters: !options.useCurrentFilters })}
+ data-test-subj="dashboardDrillDownOptions--useCurrentFilters--checkbox"
+ />
+
+ onOptionChange({ useCurrentDateRange: !options.useCurrentDateRange })}
+ data-test-subj="dashboardDrillDownOptions--useCurrentDateRange--checkbox"
+ />
+
+ onOptionChange({ openInNewTab: !options.openInNewTab })}
+ data-test-subj="dashboardDrillDownOptions--openInNewTab--checkbox"
+ />
+
>
);
diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx
index e985e9bec357a..1c8466097bd98 100644
--- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx
+++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx
@@ -52,6 +52,7 @@ export function DataViewPicker({
data-test-subj="open-data-view-picker"
onClick={() => setPopoverIsOpen(!isPopoverOpen)}
label={label}
+ size="s"
fullWidth
{...colorProp}
{...rest}
diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx
index daac202f21b66..0b81cfd66156d 100644
--- a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx
+++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx
@@ -140,6 +140,7 @@ export const FieldPicker = ({
placeholder: i18n.translate('presentationUtil.fieldSearch.searchPlaceHolder', {
defaultMessage: 'Search field names',
}),
+ compressed: true,
disabled: Boolean(selectableProps?.isLoading),
inputRef: setSearchRef,
}}
diff --git a/src/plugins/presentation_util/public/components/field_picker/field_type_filter.tsx b/src/plugins/presentation_util/public/components/field_picker/field_type_filter.tsx
index d2e929b8a9a84..4212668599a0d 100644
--- a/src/plugins/presentation_util/public/components/field_picker/field_type_filter.tsx
+++ b/src/plugins/presentation_util/public/components/field_picker/field_type_filter.tsx
@@ -63,7 +63,7 @@ export function FieldTypeFilter({
);
return (
-
+
{
return (
<>
-
- onOptionChange({ openInNewTab: !options.openInNewTab })}
- data-test-subj="urlDrilldownOpenInNewTab"
- />
-
-
-
- {txtUrlTemplateEncodeUrl}
-
- {txtUrlTemplateEncodeDescription}
- >
- }
- checked={options.encodeUrl}
- onChange={() => onOptionChange({ encodeUrl: !options.encodeUrl })}
- data-test-subj="urlDrilldownEncodeUrl"
- />
+
+
+ onOptionChange({ openInNewTab: !options.openInNewTab })}
+ data-test-subj="urlDrilldownOpenInNewTab"
+ />
+
+
+ {txtUrlTemplateEncodeUrl}
+
+ {txtUrlTemplateEncodeDescription}
+ >
+ }
+ checked={options.encodeUrl}
+ onChange={() => onOptionChange({ encodeUrl: !options.encodeUrl })}
+ data-test-subj="urlDrilldownEncodeUrl"
+ />
+
>
);
diff --git a/test/api_integration/apis/data_views/fields_for_wildcard_route/response.ts b/test/api_integration/apis/data_views/fields_for_wildcard_route/response.ts
index 2b0fa0dab4424..a80ca89ee4865 100644
--- a/test/api_integration/apis/data_views/fields_for_wildcard_route/response.ts
+++ b/test/api_integration/apis/data_views/fields_for_wildcard_route/response.ts
@@ -80,7 +80,8 @@ export default function ({ getService }: FtrProviderContext) {
},
];
- describe('fields_for_wildcard_route response', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/199413
+ describe.skip('fields_for_wildcard_route response', () => {
before(() =>
esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index')
);
diff --git a/x-pack/packages/ai-infra/product-doc-artifact-builder/src/tasks/install_elser.ts b/x-pack/packages/ai-infra/product-doc-artifact-builder/src/tasks/install_elser.ts
index 037a9e809d1e1..09dc85b816191 100644
--- a/x-pack/packages/ai-infra/product-doc-artifact-builder/src/tasks/install_elser.ts
+++ b/x-pack/packages/ai-infra/product-doc-artifact-builder/src/tasks/install_elser.ts
@@ -60,7 +60,6 @@ const waitUntilDeployed = async ({
model_id: modelId,
});
const deploymentStats = statsRes.trained_model_stats[0]?.deployment_stats;
- // @ts-expect-error deploymentStats.nodes not defined as array even if it is.
if (!deploymentStats || deploymentStats.nodes.length === 0) {
await sleep(delay);
continue;
diff --git a/x-pack/packages/kbn-cloud-security-posture/common/schema/graph/v1.ts b/x-pack/packages/kbn-cloud-security-posture/common/schema/graph/v1.ts
index 3d37331b4cc5d..076c685aca5b9 100644
--- a/x-pack/packages/kbn-cloud-security-posture/common/schema/graph/v1.ts
+++ b/x-pack/packages/kbn-cloud-security-posture/common/schema/graph/v1.ts
@@ -6,14 +6,26 @@
*/
import { schema } from '@kbn/config-schema';
+import { ApiMessageCode } from '../../types/graph/v1';
export const graphRequestSchema = schema.object({
+ nodesLimit: schema.maybe(schema.number()),
+ showUnknownTarget: schema.maybe(schema.boolean()),
query: schema.object({
- actorIds: schema.arrayOf(schema.string()),
eventIds: schema.arrayOf(schema.string()),
// TODO: use zod for range validation instead of config schema
start: schema.oneOf([schema.number(), schema.string()]),
end: schema.oneOf([schema.number(), schema.string()]),
+ esQuery: schema.maybe(
+ schema.object({
+ bool: schema.object({
+ filter: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
+ must: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
+ should: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
+ must_not: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
+ }),
+ })
+ ),
}),
});
@@ -23,6 +35,9 @@ export const graphResponseSchema = () =>
schema.oneOf([entityNodeDataSchema, groupNodeDataSchema, labelNodeDataSchema])
),
edges: schema.arrayOf(edgeDataSchema),
+ messages: schema.maybe(
+ schema.arrayOf(schema.oneOf([schema.literal(ApiMessageCode.ReachedNodesLimit)]))
+ ),
});
export const colorSchema = schema.oneOf([
diff --git a/x-pack/packages/kbn-cloud-security-posture/common/tsconfig.json b/x-pack/packages/kbn-cloud-security-posture/common/tsconfig.json
index c7cf1e9208bfc..ebec9929559f0 100644
--- a/x-pack/packages/kbn-cloud-security-posture/common/tsconfig.json
+++ b/x-pack/packages/kbn-cloud-security-posture/common/tsconfig.json
@@ -20,5 +20,6 @@
"@kbn/i18n",
"@kbn/analytics",
"@kbn/usage-collection-plugin",
+ "@kbn/es-query",
]
}
diff --git a/x-pack/packages/kbn-cloud-security-posture/common/types/graph/v1.ts b/x-pack/packages/kbn-cloud-security-posture/common/types/graph/v1.ts
index 48d1d1c49fd03..f97d11b34732c 100644
--- a/x-pack/packages/kbn-cloud-security-posture/common/types/graph/v1.ts
+++ b/x-pack/packages/kbn-cloud-security-posture/common/types/graph/v1.ts
@@ -6,6 +6,7 @@
*/
import type { TypeOf } from '@kbn/config-schema';
+import type { BoolQuery } from '@kbn/es-query';
import {
colorSchema,
edgeDataSchema,
@@ -17,13 +18,21 @@ import {
nodeShapeSchema,
} from '../../schema/graph/v1';
-export type GraphRequest = TypeOf;
-export type GraphResponse = TypeOf;
+export type GraphRequest = Omit, 'query.esQuery'> & {
+ query: { esQuery?: { bool: Partial } };
+};
+export type GraphResponse = Omit, 'messages'> & {
+ messages?: ApiMessageCode[];
+};
export type Color = typeof colorSchema.type;
export type NodeShape = TypeOf;
+export enum ApiMessageCode {
+ ReachedNodesLimit = 'REACHED_NODES_LIMIT',
+}
+
export type EntityNodeDataModel = TypeOf;
export type GroupNodeDataModel = TypeOf;
diff --git a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts
index 5054833ac7dd0..39d26509422a2 100644
--- a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts
+++ b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts
@@ -69,7 +69,6 @@ export const createRandomSamplerWrapper = (options: RandomSamplerOptions) => {
return {
[aggName]: {
- // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer`
random_sampler: {
probability,
...(options.seed ? { seed: options.seed } : {}),
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts b/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts
index 9e9744b33d940..9fb817b275a0d 100644
--- a/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts
+++ b/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts
@@ -10,6 +10,7 @@ import {
graphResponseSchema,
} from '@kbn/cloud-security-posture-common/schema/graph/latest';
import { transformError } from '@kbn/securitysolution-es-utils';
+import type { GraphRequest } from '@kbn/cloud-security-posture-common/types/graph/v1';
import { GRAPH_ROUTE_PATH } from '../../../common/constants';
import { CspRouter } from '../../types';
import { getGraph as getGraphV1 } from './v1';
@@ -39,26 +40,29 @@ export const defineGraphRoute = (router: CspRouter) =>
},
},
async (context, request, response) => {
- const { actorIds, eventIds, start, end } = request.body.query;
+ const { nodesLimit, showUnknownTarget = false } = request.body;
+ const { eventIds, start, end, esQuery } = request.body.query as GraphRequest['query'];
const cspContext = await context.csp;
const spaceId = (await cspContext.spaces?.spacesService?.getActiveSpace(request))?.id;
try {
- const { nodes, edges } = await getGraphV1(
- {
+ const resp = await getGraphV1({
+ services: {
logger: cspContext.logger,
esClient: cspContext.esClient,
},
- {
- actorIds,
+ query: {
eventIds,
spaceId,
start,
end,
- }
- );
+ esQuery,
+ },
+ showUnknownTarget,
+ nodesLimit,
+ });
- return response.ok({ body: { nodes, edges } });
+ return response.ok({ body: resp });
} catch (err) {
const error = transformError(err);
cspContext.logger.error(`Failed to fetch graph ${err}`);
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/graph/types.ts b/x-pack/plugins/cloud_security_posture/server/routes/graph/types.ts
deleted file mode 100644
index ba32664da6233..0000000000000
--- a/x-pack/plugins/cloud_security_posture/server/routes/graph/types.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type {
- EdgeDataModel,
- NodeDataModel,
-} from '@kbn/cloud-security-posture-common/types/graph/latest';
-import type { Logger, IScopedClusterClient } from '@kbn/core/server';
-import type { Writable } from '@kbn/utility-types';
-
-export interface GraphContextServices {
- logger: Logger;
- esClient: IScopedClusterClient;
-}
-
-export interface GraphContext {
- nodes: Array>;
- edges: Array>;
-}
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/graph/v1.ts b/x-pack/plugins/cloud_security_posture/server/routes/graph/v1.ts
index 5102d153c1905..b14a2ba3e06a9 100644
--- a/x-pack/plugins/cloud_security_posture/server/routes/graph/v1.ts
+++ b/x-pack/plugins/cloud_security_posture/server/routes/graph/v1.ts
@@ -8,22 +8,27 @@
import { castArray } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import type { Logger, IScopedClusterClient } from '@kbn/core/server';
+import { ApiMessageCode } from '@kbn/cloud-security-posture-common/types/graph/latest';
import type {
+ Color,
EdgeDataModel,
- NodeDataModel,
EntityNodeDataModel,
- LabelNodeDataModel,
+ GraphRequest,
+ GraphResponse,
GroupNodeDataModel,
-} from '@kbn/cloud-security-posture-common/types/graph/latest';
+ LabelNodeDataModel,
+ NodeDataModel,
+} from '@kbn/cloud-security-posture-common/types/graph/v1';
import type { EsqlToRecords } from '@elastic/elasticsearch/lib/helpers';
import type { Writable } from '@kbn/utility-types';
-import type { GraphContextServices, GraphContext } from './types';
+
+type EsQuery = GraphRequest['query']['esQuery'];
interface GraphEdge {
badge: number;
- ips: string[];
- hosts: string[];
- users: string[];
+ ips?: string[] | string;
+ hosts?: string[] | string;
+ users?: string[] | string;
actorIds: string[] | string;
action: string;
targetIds: string[] | string;
@@ -36,50 +41,75 @@ interface LabelEdges {
target: string;
}
-export const getGraph = async (
- services: GraphContextServices,
+interface GraphContextServices {
+ logger: Logger;
+ esClient: IScopedClusterClient;
+}
+
+interface GetGraphParams {
+ services: GraphContextServices;
query: {
- actorIds: string[];
eventIds: string[];
spaceId?: string;
start: string | number;
end: string | number;
- }
-): Promise<{
- nodes: NodeDataModel[];
- edges: EdgeDataModel[];
-}> => {
- const { esClient, logger } = services;
- const { actorIds, eventIds, spaceId = 'default', start, end } = query;
-
- logger.trace(
- `Fetching graph for [eventIds: ${eventIds.join(', ')}] [actorIds: ${actorIds.join(
- ', '
- )}] in [spaceId: ${spaceId}]`
- );
+ esQuery?: EsQuery;
+ };
+ showUnknownTarget: boolean;
+ nodesLimit?: number;
+}
- const results = await fetchGraph({ esClient, logger, start, end, eventIds, actorIds });
+export const getGraph = async ({
+ services: { esClient, logger },
+ query: { eventIds, spaceId = 'default', start, end, esQuery },
+ showUnknownTarget,
+ nodesLimit,
+}: GetGraphParams): Promise> => {
+ logger.trace(`Fetching graph for [eventIds: ${eventIds.join(', ')}] in [spaceId: ${spaceId}]`);
+
+ const results = await fetchGraph({
+ esClient,
+ showUnknownTarget,
+ logger,
+ start,
+ end,
+ eventIds,
+ esQuery,
+ });
// Convert results into set of nodes and edges
- const graphContext = parseRecords(logger, results.records);
-
- return { nodes: graphContext.nodes, edges: graphContext.edges };
+ return parseRecords(logger, results.records, nodesLimit);
};
interface ParseContext {
- nodesMap: Record;
- edgesMap: Record;
- edgeLabelsNodes: Record;
- labelEdges: Record;
+ readonly nodesLimit?: number;
+ readonly nodesMap: Record;
+ readonly edgesMap: Record;
+ readonly edgeLabelsNodes: Record;
+ readonly labelEdges: Record;
+ readonly messages: ApiMessageCode[];
+ readonly logger: Logger;
}
-const parseRecords = (logger: Logger, records: GraphEdge[]): GraphContext => {
- const ctx: ParseContext = { nodesMap: {}, edgeLabelsNodes: {}, edgesMap: {}, labelEdges: {} };
+const parseRecords = (
+ logger: Logger,
+ records: GraphEdge[],
+ nodesLimit?: number
+): Pick => {
+ const ctx: ParseContext = {
+ nodesLimit,
+ logger,
+ nodesMap: {},
+ edgeLabelsNodes: {},
+ edgesMap: {},
+ labelEdges: {},
+ messages: [],
+ };
- logger.trace(`Parsing records [length: ${records.length}]`);
+ logger.trace(`Parsing records [length: ${records.length}] [nodesLimit: ${nodesLimit ?? 'none'}]`);
- createNodes(logger, records, ctx);
- createEdgesAndGroups(logger, ctx);
+ createNodes(records, ctx);
+ createEdgesAndGroups(ctx);
logger.trace(
`Parsed [nodes: ${Object.keys(ctx.nodesMap).length}, edges: ${
@@ -90,7 +120,11 @@ const parseRecords = (logger: Logger, records: GraphEdge[]): GraphContext => {
// Sort groups to be first (fixes minor layout issue)
const nodes = sortNodes(ctx.nodesMap);
- return { nodes, edges: Object.values(ctx.edgesMap) };
+ return {
+ nodes,
+ edges: Object.values(ctx.edgesMap),
+ messages: ctx.messages.length > 0 ? ctx.messages : undefined,
+ };
};
const fetchGraph = async ({
@@ -98,15 +132,17 @@ const fetchGraph = async ({
logger,
start,
end,
- actorIds,
eventIds,
+ showUnknownTarget,
+ esQuery,
}: {
esClient: IScopedClusterClient;
logger: Logger;
start: string | number;
end: string | number;
- actorIds: string[];
eventIds: string[];
+ showUnknownTarget: boolean;
+ esQuery?: EsQuery;
}): Promise> => {
const query = `from logs-*
| WHERE event.action IS NOT NULL AND actor.entity.id IS NOT NULL
@@ -124,59 +160,84 @@ const fetchGraph = async ({
targetIds = target.entity.id,
eventOutcome = event.outcome,
isAlert
-| LIMIT 1000`;
+| LIMIT 1000
+| SORT isAlert DESC`;
logger.trace(`Executing query [${query}]`);
return await esClient.asCurrentUser.helpers
.esql({
columnar: false,
- filter: {
- bool: {
- must: [
+ filter: buildDslFilter(eventIds, showUnknownTarget, start, end, esQuery),
+ query,
+ // @ts-ignore - types are not up to date
+ params: [...eventIds.map((id, idx) => ({ [`al_id${idx}`]: id }))],
+ })
+ .toRecords();
+};
+
+const buildDslFilter = (
+ eventIds: string[],
+ showUnknownTarget: boolean,
+ start: string | number,
+ end: string | number,
+ esQuery?: EsQuery
+) => ({
+ bool: {
+ filter: [
+ {
+ range: {
+ '@timestamp': {
+ gte: start,
+ lte: end,
+ },
+ },
+ },
+ ...(showUnknownTarget
+ ? []
+ : [
{
- range: {
- '@timestamp': {
- gte: start,
- lte: end,
- },
+ exists: {
+ field: 'target.entity.id',
},
},
+ ]),
+ {
+ bool: {
+ should: [
+ ...(esQuery?.bool.filter?.length ||
+ esQuery?.bool.must?.length ||
+ esQuery?.bool.should?.length ||
+ esQuery?.bool.must_not?.length
+ ? [esQuery]
+ : []),
{
- bool: {
- should: [
- {
- terms: {
- 'event.id': eventIds,
- },
- },
- {
- terms: {
- 'actor.entity.id': actorIds,
- },
- },
- ],
- minimum_should_match: 1,
+ terms: {
+ 'event.id': eventIds,
},
},
],
+ minimum_should_match: 1,
},
},
- query,
- // @ts-ignore - types are not up to date
- params: [...eventIds.map((id, idx) => ({ [`al_id${idx}`]: id }))],
- })
- .toRecords();
-};
+ ],
+ },
+});
-const createNodes = (
- logger: Logger,
- records: GraphEdge[],
- context: Omit
-) => {
+const createNodes = (records: GraphEdge[], context: Omit) => {
const { nodesMap, edgeLabelsNodes, labelEdges } = context;
for (const record of records) {
+ if (context.nodesLimit !== undefined && Object.keys(nodesMap).length >= context.nodesLimit) {
+ context.logger.debug(
+ `Reached nodes limit [limit: ${context.nodesLimit}] [current: ${
+ Object.keys(nodesMap).length
+ }]`
+ );
+ context.messages.push(ApiMessageCode.ReachedNodesLimit);
+ break;
+ }
+
const { ips, hosts, users, actorIds, action, targetIds, isAlert, eventOutcome } = record;
const actorIdsArray = castArray(actorIds);
const targetIdsArray = castArray(targetIds);
@@ -190,12 +251,6 @@ const createNodes = (
}
});
- logger.trace(
- `Parsing record [actorIds: ${actorIdsArray.join(
- ', '
- )}, action: ${action}, targetIds: ${targetIdsArray.join(', ')}]`
- );
-
// Create entity nodes
[...actorIdsArray, ...targetIdsArray].forEach((id) => {
if (nodesMap[id] === undefined) {
@@ -203,10 +258,13 @@ const createNodes = (
id,
label: unknownTargets.includes(id) ? 'Unknown' : undefined,
color: isAlert ? 'danger' : 'primary',
- ...determineEntityNodeShape(id, ips ?? [], hosts ?? [], users ?? []),
+ ...determineEntityNodeShape(
+ id,
+ castArray(ips ?? []),
+ castArray(hosts ?? []),
+ castArray(users ?? [])
+ ),
};
-
- logger.trace(`Creating entity node [${id}]`);
}
});
@@ -226,8 +284,6 @@ const createNodes = (
shape: 'label',
};
- logger.trace(`Creating label node [${labelNode.id}]`);
-
nodesMap[labelNode.id] = labelNode;
edgeLabelsNodes[edgeId].push(labelNode.id);
labelEdges[labelNode.id] = { source: actorId, target: targetId };
@@ -278,7 +334,7 @@ const sortNodes = (nodesMap: Record) => {
return [...groupNodes, ...otherNodes];
};
-const createEdgesAndGroups = (logger: Logger, context: ParseContext) => {
+const createEdgesAndGroups = (context: ParseContext) => {
const { edgeLabelsNodes, edgesMap, nodesMap, labelEdges } = context;
Object.entries(edgeLabelsNodes).forEach(([edgeId, edgeLabelsIds]) => {
@@ -287,7 +343,6 @@ const createEdgesAndGroups = (logger: Logger, context: ParseContext) => {
const edgeLabelId = edgeLabelsIds[0];
connectEntitiesAndLabelNode(
- logger,
edgesMap,
nodesMap,
labelEdges[edgeLabelId].source,
@@ -300,44 +355,47 @@ const createEdgesAndGroups = (logger: Logger, context: ParseContext) => {
shape: 'group',
};
nodesMap[groupNode.id] = groupNode;
+ let groupEdgesColor: Color = 'primary';
+
+ edgeLabelsIds.forEach((edgeLabelId) => {
+ (nodesMap[edgeLabelId] as Writable).parentId = groupNode.id;
+ connectEntitiesAndLabelNode(edgesMap, nodesMap, groupNode.id, edgeLabelId, groupNode.id);
+
+ if ((nodesMap[edgeLabelId] as LabelNodeDataModel).color === 'danger') {
+ groupEdgesColor = 'danger';
+ } else if (
+ (nodesMap[edgeLabelId] as LabelNodeDataModel).color === 'warning' &&
+ groupEdgesColor !== 'danger'
+ ) {
+ // Use warning only if there's no danger color
+ groupEdgesColor = 'warning';
+ }
+ });
connectEntitiesAndLabelNode(
- logger,
edgesMap,
nodesMap,
labelEdges[edgeLabelsIds[0]].source,
groupNode.id,
- labelEdges[edgeLabelsIds[0]].target
+ labelEdges[edgeLabelsIds[0]].target,
+ groupEdgesColor
);
-
- edgeLabelsIds.forEach((edgeLabelId) => {
- (nodesMap[edgeLabelId] as Writable).parentId = groupNode.id;
- connectEntitiesAndLabelNode(
- logger,
- edgesMap,
- nodesMap,
- groupNode.id,
- edgeLabelId,
- groupNode.id
- );
- });
}
});
};
const connectEntitiesAndLabelNode = (
- logger: Logger,
edgesMap: Record,
nodesMap: Record,
sourceNodeId: string,
labelNodeId: string,
- targetNodeId: string
+ targetNodeId: string,
+ colorOverride?: Color
) => {
[
- connectNodes(nodesMap, sourceNodeId, labelNodeId),
- connectNodes(nodesMap, labelNodeId, targetNodeId),
+ connectNodes(nodesMap, sourceNodeId, labelNodeId, colorOverride),
+ connectNodes(nodesMap, labelNodeId, targetNodeId, colorOverride),
].forEach((edge) => {
- logger.trace(`Connecting nodes [${edge.source} -> ${edge.target}]`);
edgesMap[edge.id] = edge;
});
};
@@ -345,7 +403,8 @@ const connectEntitiesAndLabelNode = (
const connectNodes = (
nodesMap: Record,
sourceNodeId: string,
- targetNodeId: string
+ targetNodeId: string,
+ colorOverride?: Color
): EdgeDataModel => {
const sourceNode = nodesMap[sourceNodeId];
const targetNode = nodesMap[targetNodeId];
@@ -360,6 +419,6 @@ const connectNodes = (
id: `a(${sourceNodeId})-b(${targetNodeId})`,
source: sourceNodeId,
target: targetNodeId,
- color,
+ color: colorOverride ?? color,
};
};
diff --git a/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx b/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx
index 7f5b3f4b4b7d5..15e306bb396b7 100644
--- a/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx
+++ b/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
import { CreateIndexButton } from '../../sections/home/index_list/create_index/create_index_button';
import { ExtensionsService } from '../../../services/extensions_service';
@@ -16,11 +17,13 @@ export const NoMatch = ({
filter,
resetFilter,
extensionsService,
+ share,
}: {
loadIndices: () => void;
filter: string;
resetFilter: () => void;
extensionsService: ExtensionsService;
+ share?: SharePluginStart;
}) => {
if (filter) {
return (
@@ -62,7 +65,7 @@ export const NoMatch = ({
if (extensionsService.emptyListContent) {
return extensionsService.emptyListContent.renderContent({
- createIndexButton: ,
+ createIndexButton: ,
});
}
@@ -85,7 +88,7 @@ export const NoMatch = ({
/>
}
- actions={}
+ actions={}
/>
);
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx
index 746d684f48b75..e7201ce5d44b3 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx
@@ -7,22 +7,32 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
import { EuiButton } from '@elastic/eui';
import { CreateIndexModal } from './create_index_modal';
-export const CreateIndexButton = ({ loadIndices }: { loadIndices: () => void }) => {
+export interface CreateIndexButtonProps {
+ loadIndices: () => void;
+ share?: SharePluginStart;
+}
+
+export const CreateIndexButton = ({ loadIndices, share }: CreateIndexButtonProps) => {
const [createIndexModalOpen, setCreateIndexModalOpen] = useState(false);
+ const createIndexUrl = share?.url.locators.get('SEARCH_CREATE_INDEX')?.useUrl({});
+ const actionProp = createIndexUrl
+ ? { href: createIndexUrl }
+ : { onClick: () => setCreateIndexModalOpen(true) };
return (
<>
setCreateIndexModalOpen(true)}
key="createIndexButton"
data-test-subj="createIndexButton"
data-telemetry-id="idxMgmt-indexList-createIndexButton"
+ {...actionProp}
>
- {({ services, config, core }) => {
+ {({ services, config, core, plugins }) => {
const { extensionsService } = services;
const { application, http } = core;
+ const { share } = plugins;
const columnConfigs = getColumnConfigs({
showIndexStats: config.enableIndexStats,
showSizeAndDocCount: config.enableSizeAndDocCount,
@@ -669,7 +670,7 @@ export class IndexTable extends Component {
>
)}
-
+
@@ -714,6 +715,7 @@ export class IndexTable extends Component {
filterChanged('')}
extensionsService={extensionsService}
diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.test.tsx
index 87026e0613296..8df04b23a5435 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.test.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.test.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { fireEvent, render, screen, within } from '@testing-library/react';
+import { fireEvent, render, screen, within, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IncludeExcludeRow, IncludeExcludeRowProps } from './include_exclude_options';
@@ -152,4 +152,168 @@ describe('IncludeExcludeComponent', () => {
});
expect(onUpdateSpy).toHaveBeenCalledTimes(2);
});
+
+ it('should prevent identical include and exclude values on change when making single selections', async () => {
+ renderIncludeExcludeRow({
+ include: undefined,
+ exclude: undefined,
+ isNumberField: false,
+ tableRows,
+ });
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Include values' }));
+ await userEvent.click(screen.getByRole('option', { name: 'ABC' }));
+ expect(screen.getByTestId('lens-include-terms-combobox')).toHaveTextContent('ABC');
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Exclude values' }));
+ await userEvent.click(screen.getByRole('option', { name: 'ABC' }));
+ expect(screen.getByTestId('lens-exclude-terms-combobox')).toHaveTextContent('ABC');
+
+ expect(screen.getByTestId('lens-include-terms-combobox')).not.toHaveTextContent('ABC');
+
+ expect(onUpdateSpy).toHaveBeenCalledTimes(3);
+ });
+
+ it('should prevent identical include and exclude values on change when making multiple selections', async () => {
+ renderIncludeExcludeRow({
+ include: undefined,
+ exclude: undefined,
+ isNumberField: false,
+ tableRows,
+ });
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Include values' }));
+ await userEvent.click(screen.getByRole('option', { name: 'ABC' }));
+ expect(screen.getByTestId('lens-include-terms-combobox')).toHaveTextContent('ABC');
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Include values' }));
+ await userEvent.click(screen.getByRole('option', { name: 'FEF' }));
+ expect(screen.getByTestId('lens-include-terms-combobox')).toHaveTextContent('FEF');
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Exclude values' }));
+ await userEvent.click(screen.getByRole('option', { name: 'ABC' }));
+ expect(screen.getByTestId('lens-include-terms-combobox')).not.toHaveTextContent('ABC');
+
+ expect(screen.getByTestId('lens-exclude-terms-combobox')).toHaveTextContent('ABC');
+
+ expect(onUpdateSpy).toHaveBeenCalledTimes(4);
+ });
+
+ it('should prevent identical include and exclude values on create option', async () => {
+ renderIncludeExcludeRow({
+ include: undefined,
+ exclude: undefined,
+ isNumberField: false,
+ tableRows,
+ });
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Include values' }));
+ await userEvent.type(screen.getByRole('combobox', { name: 'Include values' }), 'test{enter}');
+ expect(screen.getByTestId('lens-include-terms-combobox')).toHaveTextContent('test');
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Exclude values' }));
+ await userEvent.type(screen.getByRole('combobox', { name: 'Exclude values' }), 'test{enter}');
+ expect(screen.getByTestId('lens-exclude-terms-combobox')).toHaveTextContent('test');
+
+ expect(screen.getByTestId('lens-include-terms-combobox')).not.toHaveTextContent('test');
+
+ expect(onUpdateSpy).toHaveBeenCalledTimes(3);
+ });
+
+ it('should prevent identical include and exclude values when creating multiple options', async () => {
+ renderIncludeExcludeRow({
+ include: undefined,
+ exclude: undefined,
+ isNumberField: false,
+ tableRows,
+ });
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Include values' }));
+ await userEvent.type(screen.getByRole('combobox', { name: 'Include values' }), 'test{enter}');
+ expect(screen.getByTestId('lens-include-terms-combobox')).toHaveTextContent('test');
+
+ await userEvent.type(screen.getByRole('combobox', { name: 'Include values' }), 'test1{enter}');
+ expect(screen.getByTestId('lens-include-terms-combobox')).toHaveTextContent('test1');
+
+ await userEvent.click(screen.getByRole('combobox', { name: 'Exclude values' }));
+ await userEvent.type(screen.getByRole('combobox', { name: 'Exclude values' }), 'test1{enter}');
+ expect(screen.getByTestId('lens-exclude-terms-combobox')).toHaveTextContent('test1');
+
+ expect(screen.getByTestId('lens-include-terms-combobox')).not.toHaveTextContent('test1');
+
+ expect(onUpdateSpy).toHaveBeenCalledTimes(4);
+ });
+
+ it('should prevent identical include value on exclude regex value change', async () => {
+ jest.useFakeTimers();
+
+ renderIncludeExcludeRow({
+ include: [''],
+ exclude: [''],
+ includeIsRegex: true,
+ excludeIsRegex: true,
+ tableRows,
+ });
+
+ const includeRegexInput = screen.getByTestId('lens-include-terms-regex-input');
+ const excludeRegexInput = screen.getByTestId('lens-exclude-terms-regex-input');
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+
+ await user.type(includeRegexInput, 'test.*');
+ act(() => {
+ jest.advanceTimersByTime(256);
+ });
+ expect(includeRegexInput).toHaveValue('test.*');
+ expect(onUpdateSpy).toHaveBeenCalledWith('include', ['test.*'], 'includeIsRegex', true);
+
+ await user.type(excludeRegexInput, 'test.*');
+ act(() => {
+ jest.advanceTimersByTime(256);
+ });
+ expect(excludeRegexInput).toHaveValue('test.*');
+ expect(onUpdateSpy).toHaveBeenCalledWith('exclude', ['test.*'], 'excludeIsRegex', true);
+
+ expect(includeRegexInput).toHaveValue('');
+ expect(onUpdateSpy).toHaveBeenCalledWith('include', [''], 'includeIsRegex', true);
+
+ expect(onUpdateSpy).toHaveBeenCalledTimes(3);
+
+ jest.useRealTimers();
+ });
+
+ it('should prevent identical exclude value on include regex value change', async () => {
+ jest.useFakeTimers();
+
+ renderIncludeExcludeRow({
+ include: [''],
+ exclude: [''],
+ includeIsRegex: true,
+ excludeIsRegex: true,
+ tableRows,
+ });
+
+ const includeRegexInput = screen.getByTestId('lens-include-terms-regex-input');
+ const excludeRegexInput = screen.getByTestId('lens-exclude-terms-regex-input');
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+
+ await user.type(excludeRegexInput, 'test.*');
+ act(() => {
+ jest.advanceTimersByTime(256);
+ });
+ expect(excludeRegexInput).toHaveValue('test.*');
+ expect(onUpdateSpy).toHaveBeenCalledWith('exclude', ['test.*'], 'excludeIsRegex', true);
+
+ await user.type(includeRegexInput, 'test.*');
+ act(() => {
+ jest.advanceTimersByTime(256);
+ });
+ expect(includeRegexInput).toHaveValue('test.*');
+ expect(onUpdateSpy).toHaveBeenCalledWith('include', ['test.*'], 'includeIsRegex', true);
+
+ expect(excludeRegexInput).toHaveValue('');
+ expect(onUpdateSpy).toHaveBeenCalledWith('exclude', [''], 'excludeIsRegex', true);
+
+ expect(onUpdateSpy).toHaveBeenCalledTimes(3);
+ jest.useRealTimers();
+ });
});
diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.tsx
index 41f521088af94..b2a8abb62c1ae 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/include_exclude_options.tsx
@@ -99,68 +99,77 @@ export const IncludeExcludeRow = ({
selectedOptions: IncludeExcludeOptions[],
operation: 'include' | 'exclude'
) => {
- const options = {
- ...includeExcludeSelectedOptions,
- [operation]: selectedOptions,
- };
- setIncludeExcludeSelectedOptions(options);
- const terms = selectedOptions.map((option) => {
- if (!Number.isNaN(Number(option.label))) {
- return Number(option.label);
- }
- return option.label;
+ const otherOperation = operation === 'include' ? 'exclude' : 'include';
+ const otherSelectedOptions = includeExcludeSelectedOptions[otherOperation] ?? [];
+ const hasIdenticalOptions = selectedOptions.some((option) => {
+ return otherSelectedOptions.some((otherOption) => otherOption.label === option.label);
});
- const param = `${operation}IsRegex`;
- updateParams(operation, terms, param, false);
- };
-
- const onCreateOption = (
- searchValue: string,
- flattenedOptions: IncludeExcludeOptions[] = [],
- operation: 'include' | 'exclude'
- ) => {
- const newOption = {
- label: searchValue,
- };
- let includeExcludeOptions = [];
+ const otherSelectedNonIdenticalOptions = hasIdenticalOptions
+ ? otherSelectedOptions.filter(
+ (otherOption) => !selectedOptions.some((option) => option.label === otherOption.label)
+ )
+ : otherSelectedOptions;
- const includeORExcludeSelectedOptions = includeExcludeSelectedOptions[operation] ?? [];
- includeExcludeOptions = [...includeORExcludeSelectedOptions, newOption];
const options = {
- ...includeExcludeSelectedOptions,
- [operation]: includeExcludeOptions,
+ [otherOperation]: otherSelectedNonIdenticalOptions,
+ [operation]: selectedOptions,
};
setIncludeExcludeSelectedOptions(options);
- const terms = includeExcludeOptions.map((option) => {
- if (!Number.isNaN(Number(option.label))) {
- return Number(option.label);
- }
- return option.label;
- });
+ const getTerms = (updatedSelectedOptions: IncludeExcludeOptions[]) =>
+ updatedSelectedOptions.map((option) => {
+ if (!Number.isNaN(Number(option.label))) {
+ return Number(option.label);
+ }
+ return option.label;
+ });
+
+ const terms = getTerms(selectedOptions);
const param = `${operation}IsRegex`;
updateParams(operation, terms, param, false);
+
+ if (hasIdenticalOptions) {
+ const otherTerms = getTerms(otherSelectedNonIdenticalOptions);
+ const otherParam = `${otherOperation}IsRegex`;
+ updateParams(otherOperation, otherTerms, otherParam, false);
+ }
+ };
+
+ const onCreateOption = (searchValue: string, operation: 'include' | 'exclude') => {
+ const newOption = { label: searchValue };
+ const selectedOptions = [...(includeExcludeSelectedOptions[operation] ?? []), newOption];
+ onChangeIncludeExcludeOptions(selectedOptions, operation);
};
const onIncludeRegexChangeToDebounce = useCallback(
(newIncludeValue: string | number | undefined) => {
+ const isEqualToExcludeValue = newIncludeValue === regex.exclude;
+ const excludeValue = isEqualToExcludeValue ? '' : regex.exclude;
setRegex({
- ...regex,
+ exclude: excludeValue,
include: newIncludeValue,
});
updateParams('include', [newIncludeValue ?? ''], 'includeIsRegex', true);
+ if (isEqualToExcludeValue) {
+ updateParams('exclude', [''], 'excludeIsRegex', true);
+ }
},
[regex, updateParams]
);
const onExcludeRegexChangeToDebounce = useCallback(
(newExcludeValue: string | number | undefined) => {
+ const isEqualToIncludeValue = newExcludeValue === regex.include;
+ const includeValue = isEqualToIncludeValue ? '' : regex.include;
setRegex({
- ...regex,
+ include: includeValue,
exclude: newExcludeValue,
});
updateParams('exclude', [newExcludeValue ?? ''], 'excludeIsRegex', true);
+ if (isEqualToIncludeValue) {
+ updateParams('include', [''], 'includeIsRegex', true);
+ }
},
[regex, updateParams]
);
@@ -247,9 +256,7 @@ export const IncludeExcludeRow = ({
options={termsOptions}
selectedOptions={includeExcludeSelectedOptions.include}
onChange={(options) => onChangeIncludeExcludeOptions(options, 'include')}
- onCreateOption={(searchValue, options) =>
- onCreateOption(searchValue, options, 'include')
- }
+ onCreateOption={(searchValue) => onCreateOption(searchValue, 'include')}
isClearable={true}
data-test-subj="lens-include-terms-combobox"
autoFocus
@@ -300,6 +307,7 @@ export const IncludeExcludeRow = ({
defaultMessage: 'Enter a regex to filter values',
}
)}
+ data-test-subj="lens-exclude-terms-regex-input"
value={excludeRegexValue}
onChange={(e) => {
onExcludeRegexValueChange(e.target.value);
@@ -322,9 +330,7 @@ export const IncludeExcludeRow = ({
options={termsOptions}
selectedOptions={includeExcludeSelectedOptions.exclude}
onChange={(options) => onChangeIncludeExcludeOptions(options, 'exclude')}
- onCreateOption={(searchValue, options) =>
- onCreateOption(searchValue, options, 'exclude')
- }
+ onCreateOption={(searchValue) => onCreateOption(searchValue, 'exclude')}
isClearable={true}
data-test-subj="lens-exclude-terms-combobox"
autoFocus
diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts
index 0a3632efe9195..abdffd19eca76 100644
--- a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts
+++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts
@@ -34,7 +34,7 @@ export const transformElasticNamedSearchToListItem = ({
}: TransformElasticMSearchToListItemOptions): SearchListItemArraySchema => {
return value.map((singleValue, index) => {
const matchingHits = response.hits.hits.filter((hit) => {
- if (hit.matched_queries != null) {
+ if (hit.matched_queries != null && Array.isArray(hit.matched_queries)) {
return hit.matched_queries.some((matchedQuery) => {
const [matchedQueryIndex] = matchedQuery.split('.');
return matchedQueryIndex === `${index}`;
diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss b/x-pack/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss
deleted file mode 100644
index 9f9f16dff7e13..0000000000000
--- a/x-pack/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-.mlChartTooltip {
- @include euiToolTipStyle('s');
- @include euiFontSizeXS;
- padding: 0;
- transition: opacity $euiAnimSpeedNormal;
- pointer-events: none;
- user-select: none;
- max-width: 512px;
-
- &__list {
- margin: $euiSizeXS;
- padding-bottom: $euiSizeXS;
- }
-
- &__header {
- font-weight: $euiFontWeightBold;
- padding: $euiSizeXS ($euiSizeXS * 2);
- margin-bottom: $euiSizeXS;
- border-bottom: $euiBorderThin solid transparentize($euiBorderColor, .8);
- }
-
- &__item {
- display: flex;
- padding: 3px;
- box-sizing: border-box;
- border-left: $euiSizeXS solid transparent;
- }
-
- &__label {
- min-width: 1px;
- }
-
- &__value {
- font-weight: $euiFontWeightBold;
- text-align: right;
- font-feature-settings: 'tnum';
- margin-left: 8px;
- }
-
- &__rowHighlighted {
- background-color: transparentize($euiColorGhost, .9);
- }
-
- &--hidden {
- opacity: 0;
- }
-}
diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/_index.scss b/x-pack/plugins/ml/public/application/components/chart_tooltip/_index.scss
deleted file mode 100644
index 11b36a0a21001..0000000000000
--- a/x-pack/plugins/ml/public/application/components/chart_tooltip/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import 'chart_tooltip';
\ No newline at end of file
diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx
index 0c6fe9095f4e2..f279175d01107 100644
--- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx
+++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx
@@ -9,14 +9,14 @@ import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import TooltipTrigger from 'react-popper-tooltip';
+import type { ChildrenArg, TooltipTriggerProps } from 'react-popper-tooltip/dist/types';
+
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { TooltipValueFormatter } from '@elastic/charts';
-import './_index.scss';
-
-import type { ChildrenArg, TooltipTriggerProps } from 'react-popper-tooltip/dist/types';
import type { ChartTooltipValue, TooltipData } from './chart_tooltip_service';
import { ChartTooltipService } from './chart_tooltip_service';
+import { useChartTooltipStyles } from './chart_tooltip_styles';
const renderHeader = (headerData?: ChartTooltipValue, formatter?: TooltipValueFormatter) => {
if (!headerData) {
@@ -30,17 +30,26 @@ const renderHeader = (headerData?: ChartTooltipValue, formatter?: TooltipValueFo
* Pure component for rendering the tooltip content with a custom layout across the ML plugin.
*/
export const FormattedTooltip: FC<{ tooltipData: TooltipData }> = ({ tooltipData }) => {
+ const {
+ mlChartTooltip,
+ mlChartTooltipList,
+ mlChartTooltipHeader,
+ mlChartTooltipItem,
+ mlChartTooltipLabel,
+ mlChartTooltipValue,
+ } = useChartTooltipStyles();
+
return (
-
+
{tooltipData.length > 0 && tooltipData[0].skipHeader === undefined && (
-
{renderHeader(tooltipData[0])}
+
{renderHeader(tooltipData[0])}
)}
{tooltipData.length > 1 && (
-
+
{tooltipData
.slice(1)
.map(({ label, value, color, isHighlighted, seriesIdentifier, valueAccessor }) => {
- const classes = classNames('mlChartTooltip__item', {
+ const classes = classNames({
// eslint-disable-next-line @typescript-eslint/naming-convention
echTooltip__rowHighlighted: isHighlighted,
});
@@ -52,16 +61,21 @@ export const FormattedTooltip: FC<{ tooltipData: TooltipData }> = ({ tooltipData
return (
-
+
{label}
-
+
{renderValue}
diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_styles.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_styles.ts
new file mode 100644
index 0000000000000..c53bdb5242f3c
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_styles.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 { css } from '@emotion/react';
+
+import { mathWithUnits, transparentize, useEuiTheme } from '@elastic/eui';
+// @ts-expect-error style types not defined
+import { euiToolTipStyles } from '@elastic/eui/lib/components/tool_tip/tool_tip.styles';
+
+import { useCurrentEuiThemeVars } from '@kbn/ml-kibana-theme';
+
+import { useMlKibana } from '../../contexts/kibana';
+
+export const useChartTooltipStyles = () => {
+ const euiThemeContext = useEuiTheme();
+ const {
+ services: { theme },
+ } = useMlKibana();
+ const { euiTheme } = useCurrentEuiThemeVars(theme);
+ const euiStyles = euiToolTipStyles(euiThemeContext);
+
+ return {
+ mlChartTooltip: css([
+ euiStyles.euiToolTip,
+ {
+ fontSize: euiTheme.euiFontSizeXS,
+ padding: 0,
+ transition: `opacity ${euiTheme.euiAnimSpeedNormal}`,
+ pointerEvents: 'none',
+ userSelect: 'none',
+ maxWidth: '512px',
+ position: 'relative',
+ },
+ ]),
+ mlChartTooltipList: css({
+ margin: euiTheme.euiSizeXS,
+ paddingBottom: euiTheme.euiSizeXS,
+ }),
+ mlChartTooltipHeader: css({
+ fontWeight: euiTheme.euiFontWeightBold,
+ padding: `${euiTheme.euiSizeXS} ${mathWithUnits(euiTheme.euiSizeS, (x) => x * 2)}`,
+ marginBottom: euiTheme.euiSizeXS,
+ borderBottom: `1px solid ${transparentize(euiTheme.euiBorderColor, 0.8)}`,
+ }),
+ mlChartTooltipItem: css({
+ display: 'flex',
+ padding: '3px',
+ boxSizing: 'border-box',
+ borderLeft: `${euiTheme.euiSizeXS} solid transparent`,
+ }),
+ mlChartTooltipLabel: css({
+ minWidth: '1px',
+ }),
+ mlChartTooltipValue: css({
+ fontWeight: euiTheme.euiFontWeightBold,
+ textAlign: 'right',
+ fontFeatureSettings: 'tnum',
+ marginLeft: '8px',
+ }),
+ };
+};
diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.scss b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.scss
deleted file mode 100644
index 322cdb4971f05..0000000000000
--- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-.mlScatterplotMatrix {
- overflow-x: auto;
-
- .vega-bind span {
- font-size: $euiFontSizeXS;
- padding: 0 $euiSizeXS;
- }
-}
\ No newline at end of file
diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx
index dab7dc4117083..763addd4aaa87 100644
--- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx
+++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx
@@ -7,6 +7,7 @@
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { css } from '@emotion/react';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
@@ -35,6 +36,7 @@ import {
type RuntimeMappings,
} from '@kbn/ml-runtime-field-utils';
import { getProcessedFields } from '@kbn/ml-data-grid';
+import { euiThemeVars } from '@kbn/ui-theme';
import { useCurrentThemeVars, useMlApi, useMlKibana } from '../../contexts/kibana';
@@ -48,7 +50,17 @@ import {
OUTLIER_SCORE_FIELD,
} from './scatterplot_matrix_vega_lite_spec';
-import './scatterplot_matrix.scss';
+const cssOverrides = css({
+ // Prevent the chart from overflowing the container
+ overflowX: 'auto',
+ // Overrides for the outlier threshold slider
+ '.vega-bind': {
+ span: {
+ fontSize: euiThemeVars.euiFontSizeXS,
+ padding: `0 ${euiThemeVars.euiSizeXS}`,
+ },
+ },
+});
const SCATTERPLOT_MATRIX_DEFAULT_FIELDS = 4;
const SCATTERPLOT_MATRIX_DEFAULT_FETCH_SIZE = 1000;
@@ -413,7 +425,7 @@ export const ScatterplotMatrix: FC
= ({
) : (
diff --git a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx
index e9db6b4e4f590..f7e95e3eda52c 100644
--- a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx
+++ b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx
@@ -182,6 +182,7 @@ export const ExpandedRow: FC = ({ item }) => {
key: `${perDeploymentStat.deployment_id}_${nodeName}`,
...perDeploymentStat,
...modelSizeStats,
+ // @ts-expect-error `throughput_last_minute` is not declared in ES Types
node: {
...pick(n, [
'average_inference_time_ms',
diff --git a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts
index 6e2931dbbe06b..6e9121a133bb8 100644
--- a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts
+++ b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts
@@ -181,6 +181,7 @@ export class MemoryUsageService {
const mlNodes = Object.entries(response.nodes).filter(([, node]) => node.roles.includes('ml'));
+ // @ts-expect-error `throughput_last_minute` is not declared in ES Types
const nodeDeploymentStatsResponses: NodeDeploymentStatsResponse[] = mlNodes.map(
([nodeId, node]) => {
const nodeFields = pick(node, NODE_FIELDS) as RequiredNodeFields;
diff --git a/x-pack/plugins/search_indices/public/analytics/constants.ts b/x-pack/plugins/search_indices/public/analytics/constants.ts
index d64019d6ef676..0da7aedf19328 100644
--- a/x-pack/plugins/search_indices/public/analytics/constants.ts
+++ b/x-pack/plugins/search_indices/public/analytics/constants.ts
@@ -12,9 +12,9 @@ export enum AnalyticsEvents {
startCreateIndexPageModifyIndexName = 'start_modify_index_name',
startCreateIndexClick = 'start_create_index',
startCreateIndexLanguageSelect = 'start_code_lang_select',
+ startCreateIndexRunInConsole = 'start_cta_run_in_console',
startCreateIndexCodeCopyInstall = 'start_code_copy_install',
startCreateIndexCodeCopy = 'start_code_copy',
- startCreateIndexRunInConsole = 'start_cta_run_in_console',
startCreateIndexCreatedRedirect = 'start_index_created_api',
startFileUploadClick = 'start_file_upload',
indexDetailsInstallCodeCopy = 'index_details_code_copy_install',
@@ -23,4 +23,15 @@ export enum AnalyticsEvents {
indexDetailsNavDataTab = 'index_details_nav_data_tab',
indexDetailsNavSettingsTab = 'index_details_nav_settings_tab',
indexDetailsNavMappingsTab = 'index_details_nav_mappings_tab',
+ createIndexPageOpened = 'create_index_page_opened',
+ createIndexShowCodeClick = 'create_index_show_code',
+ createIndexShowUIClick = 'create_index_show_create_index_ui',
+ createIndexPageModifyIndexName = 'create_index_modify_index_name',
+ createIndexCreateIndexClick = 'create_index_click_create',
+ createIndexLanguageSelect = 'create_index_code_lang_select',
+ createIndexRunInConsole = 'create_index_run_in_console',
+ createIndexCodeCopyInstall = 'create_index_copy_install',
+ createIndexCodeCopy = 'create_index_code_copy',
+ createIndexFileUploadClick = 'create_index_file_upload',
+ createIndexIndexCreatedRedirect = 'create_index_created_api',
}
diff --git a/x-pack/plugins/search_indices/public/components/create_index/create_index.tsx b/x-pack/plugins/search_indices/public/components/create_index/create_index.tsx
new file mode 100644
index 0000000000000..d8ce8073c691e
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/create_index/create_index.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 React, { useCallback, useState } from 'react';
+
+import type { IndicesStatusResponse, UserStartPrivilegesResponse } from '../../../common';
+
+import { AnalyticsEvents } from '../../analytics/constants';
+import { AvailableLanguages } from '../../code_examples';
+import { useKibana } from '../../hooks/use_kibana';
+import { useUsageTracker } from '../../hooks/use_usage_tracker';
+import { CreateIndexFormState } from '../../types';
+import { generateRandomIndexName } from '../../utils/indices';
+import { getDefaultCodingLanguage } from '../../utils/language';
+
+import { CreateIndexPanel } from '../shared/create_index_panel';
+
+import { CreateIndexCodeView } from './create_index_code_view';
+import { CreateIndexUIView } from './create_index_ui_view';
+
+function initCreateIndexState() {
+ const defaultIndexName = generateRandomIndexName();
+ return {
+ indexName: defaultIndexName,
+ defaultIndexName,
+ codingLanguage: getDefaultCodingLanguage(),
+ };
+}
+
+export interface CreateIndexProps {
+ indicesData?: IndicesStatusResponse;
+ userPrivileges?: UserStartPrivilegesResponse;
+}
+
+enum CreateIndexViewMode {
+ UI = 'ui',
+ Code = 'code',
+}
+
+export const CreateIndex = ({ indicesData, userPrivileges }: CreateIndexProps) => {
+ const { application } = useKibana().services;
+ const [createIndexView, setCreateIndexView] = useState(
+ userPrivileges?.privileges.canCreateIndex === false
+ ? CreateIndexViewMode.Code
+ : CreateIndexViewMode.UI
+ );
+ const [formState, setFormState] = useState(initCreateIndexState);
+ const usageTracker = useUsageTracker();
+ const onChangeView = useCallback(
+ (id: string) => {
+ switch (id) {
+ case CreateIndexViewMode.UI:
+ usageTracker.click(AnalyticsEvents.createIndexShowUIClick);
+ setCreateIndexView(CreateIndexViewMode.UI);
+ return;
+ case CreateIndexViewMode.Code:
+ usageTracker.click(AnalyticsEvents.createIndexShowCodeClick);
+ setCreateIndexView(CreateIndexViewMode.Code);
+ return;
+ }
+ },
+ [usageTracker]
+ );
+ const onChangeCodingLanguage = useCallback(
+ (language: AvailableLanguages) => {
+ setFormState({
+ ...formState,
+ codingLanguage: language,
+ });
+ usageTracker.count([
+ AnalyticsEvents.createIndexLanguageSelect,
+ `${AnalyticsEvents.createIndexLanguageSelect}_${language}`,
+ ]);
+ },
+ [usageTracker, formState, setFormState]
+ );
+ const onClose = useCallback(() => {
+ application.navigateToApp('management', { deepLinkId: 'index_management' });
+ }, [application]);
+
+ return (
+
+ {createIndexView === CreateIndexViewMode.UI && (
+
+ )}
+ {createIndexView === CreateIndexViewMode.Code && (
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/create_index/create_index_code_view.tsx b/x-pack/plugins/search_indices/public/components/create_index/create_index_code_view.tsx
new file mode 100644
index 0000000000000..cdadfbdc146f6
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/create_index/create_index_code_view.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 type { IndicesStatusResponse } from '../../../common';
+import {
+ CreateIndexCodeView as SharedCreateIndexCodeView,
+ CreateIndexCodeViewProps as SharedCreateIndexCodeViewProps,
+} from '../shared/create_index_code_view';
+
+import { useIndicesRedirect } from './hooks/use_indices_redirect';
+
+export interface CreateIndexCodeViewProps extends SharedCreateIndexCodeViewProps {
+ indicesData?: IndicesStatusResponse;
+}
+
+export const CreateIndexCodeView = ({ indicesData, ...props }: CreateIndexCodeViewProps) => {
+ useIndicesRedirect(indicesData);
+
+ return ;
+};
diff --git a/x-pack/plugins/search_indices/public/components/create_index/create_index_page.tsx b/x-pack/plugins/search_indices/public/components/create_index/create_index_page.tsx
new file mode 100644
index 0000000000000..d8601e95760d7
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/create_index/create_index_page.tsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui';
+import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
+
+import { useKibana } from '../../hooks/use_kibana';
+import { useIndicesStatusQuery } from '../../hooks/api/use_indices_status';
+import { useUserPrivilegesQuery } from '../../hooks/api/use_user_permissions';
+import { LoadIndicesStatusError } from '../shared/load_indices_status_error';
+
+import { CreateIndex } from './create_index';
+import { usePageChrome } from '../../hooks/use_page_chrome';
+import { IndexManagementBreadcrumbs } from '../shared/breadcrumbs';
+
+const CreateIndexLabel = i18n.translate('xpack.searchIndices.createIndex.docTitle', {
+ defaultMessage: 'Create Index',
+});
+
+export const CreateIndexPage = () => {
+ const { console: consolePlugin } = useKibana().services;
+ const {
+ data: indicesData,
+ isInitialLoading,
+ isError: hasIndicesStatusFetchError,
+ error: indicesFetchError,
+ } = useIndicesStatusQuery();
+ const { data: userPrivileges } = useUserPrivilegesQuery();
+
+ const embeddableConsole = useMemo(
+ () => (consolePlugin?.EmbeddableConsole ? : null),
+ [consolePlugin]
+ );
+ usePageChrome(CreateIndexLabel, [...IndexManagementBreadcrumbs, { text: CreateIndexLabel }]);
+
+ return (
+
+
+ {isInitialLoading && }
+ {hasIndicesStatusFetchError && }
+ {!isInitialLoading && !hasIndicesStatusFetchError && (
+
+ )}
+
+ {embeddableConsole}
+
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/create_index/create_index_ui_view.tsx b/x-pack/plugins/search_indices/public/components/create_index/create_index_ui_view.tsx
new file mode 100644
index 0000000000000..08073c0e84794
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/create_index/create_index_ui_view.tsx
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useCallback, useState } from 'react';
+
+import type { UserStartPrivilegesResponse } from '../../../common';
+import { AnalyticsEvents } from '../../analytics/constants';
+import { CreateIndexFormState } from '../../types';
+import { CreateIndexForm } from '../shared/create_index_form';
+import { useUsageTracker } from '../../hooks/use_usage_tracker';
+import { isValidIndexName } from '../../utils/indices';
+import { useCreateIndex } from '../shared/hooks/use_create_index';
+
+import { useKibana } from '../../hooks/use_kibana';
+
+export interface CreateIndexUIViewProps {
+ formState: CreateIndexFormState;
+ setFormState: (value: CreateIndexFormState) => void;
+ userPrivileges?: UserStartPrivilegesResponse;
+}
+
+export const CreateIndexUIView = ({
+ formState,
+ setFormState,
+ userPrivileges,
+}: CreateIndexUIViewProps) => {
+ const [indexNameHasError, setIndexNameHasError] = useState(false);
+ const { application } = useKibana().services;
+ const usageTracker = useUsageTracker();
+ const { createIndex, isLoading } = useCreateIndex();
+ const onIndexNameChange = (e: React.ChangeEvent) => {
+ const newIndexName = e.target.value;
+ setFormState({ ...formState, indexName: e.target.value });
+ const invalidIndexName = !isValidIndexName(newIndexName);
+ if (indexNameHasError !== invalidIndexName) {
+ setIndexNameHasError(invalidIndexName);
+ }
+ };
+ const onCreateIndex = useCallback(
+ (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!isValidIndexName(formState.indexName)) {
+ return;
+ }
+ usageTracker.click(AnalyticsEvents.createIndexCreateIndexClick);
+
+ if (formState.defaultIndexName !== formState.indexName) {
+ usageTracker.click(AnalyticsEvents.createIndexPageModifyIndexName);
+ }
+
+ createIndex({ indexName: formState.indexName });
+ },
+ [usageTracker, createIndex, formState.indexName, formState.defaultIndexName]
+ );
+ const onFileUpload = useCallback(() => {
+ usageTracker.click(AnalyticsEvents.createIndexFileUploadClick);
+ application.navigateToApp('ml', { path: 'filedatavisualizer' });
+ }, [usageTracker, application]);
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/create_index/hooks/use_indices_redirect.tsx b/x-pack/plugins/search_indices/public/components/create_index/hooks/use_indices_redirect.tsx
new file mode 100644
index 0000000000000..4246976209a9f
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/create_index/hooks/use_indices_redirect.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useEffect, useState } from 'react';
+
+import type { IndicesStatusResponse } from '../../../../common';
+
+import { useKibana } from '../../../hooks/use_kibana';
+
+import { getFirstNewIndexName } from '../../../utils/indices';
+import { navigateToIndexDetails } from '../../utils';
+import { useUsageTracker } from '../../../contexts/usage_tracker_context';
+import { AnalyticsEvents } from '../../../analytics/constants';
+
+export const useIndicesRedirect = (indicesStatus?: IndicesStatusResponse) => {
+ const { application, http } = useKibana().services;
+ const [initialStatus, setInitialStatus] = useState(undefined);
+ const [hasDoneRedirect, setHasDoneRedirect] = useState(() => false);
+ const usageTracker = useUsageTracker();
+ return useEffect(() => {
+ if (hasDoneRedirect) {
+ return;
+ }
+ if (!indicesStatus) {
+ return;
+ }
+ if (initialStatus === undefined) {
+ setInitialStatus(indicesStatus);
+ return;
+ }
+ const newIndexName = getFirstNewIndexName(initialStatus.indexNames, indicesStatus.indexNames);
+ if (newIndexName) {
+ navigateToIndexDetails(application, http, newIndexName);
+ setHasDoneRedirect(true);
+ usageTracker.click(AnalyticsEvents.createIndexIndexCreatedRedirect);
+ return;
+ }
+ }, [
+ application,
+ http,
+ indicesStatus,
+ initialStatus,
+ setHasDoneRedirect,
+ usageTracker,
+ hasDoneRedirect,
+ ]);
+};
diff --git a/x-pack/plugins/search_indices/public/components/indices/details_page.tsx b/x-pack/plugins/search_indices/public/components/indices/details_page.tsx
index ad5e174dd6e4a..c672bb51493f6 100644
--- a/x-pack/plugins/search_indices/public/components/indices/details_page.tsx
+++ b/x-pack/plugins/search_indices/public/components/indices/details_page.tsx
@@ -37,20 +37,14 @@ import { SearchIndexDetailsPageMenuItemPopover } from './details_page_menu_item'
import { useIndexDocumentSearch } from '../../hooks/api/use_document_search';
import { useUsageTracker } from '../../contexts/usage_tracker_context';
import { AnalyticsEvents } from '../../analytics/constants';
+import { usePageChrome } from '../../hooks/use_page_chrome';
+import { IndexManagementBreadcrumbs } from '../shared/breadcrumbs';
export const SearchIndexDetailsPage = () => {
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
const tabId = decodeURIComponent(useParams<{ tabId: string }>().tabId);
- const {
- console: consolePlugin,
- docLinks,
- application,
- history,
- share,
- chrome,
- serverless,
- } = useKibana().services;
+ const { console: consolePlugin, docLinks, application, history, share } = useKibana().services;
const {
data: index,
refetch,
@@ -82,23 +76,12 @@ export const SearchIndexDetailsPage = () => {
setHasDocuments(!(!isInitialLoading && indexDocuments?.results?.data.length === 0));
}, [indexDocuments, isInitialLoading, setHasDocuments, setDocumentsLoading]);
- useEffect(() => {
- chrome.docTitle.change(indexName);
-
- if (serverless) {
- serverless.setBreadcrumbs([
- {
- text: i18n.translate('xpack.searchIndices.indexBreadcrumbLabel', {
- defaultMessage: 'Index Management',
- }),
- href: '/app/management/data/index_management/indices',
- },
- {
- text: indexName,
- },
- ]);
- }
- }, [chrome, indexName, serverless]);
+ usePageChrome(indexName, [
+ ...IndexManagementBreadcrumbs,
+ {
+ text: indexName,
+ },
+ ]);
const usageTracker = useUsageTracker();
diff --git a/x-pack/plugins/search_indices/public/components/indices/indices_router.tsx b/x-pack/plugins/search_indices/public/components/indices_router.tsx
similarity index 80%
rename from x-pack/plugins/search_indices/public/components/indices/indices_router.tsx
rename to x-pack/plugins/search_indices/public/components/indices_router.tsx
index 51527a7d2ef8e..56ccb3c0674e6 100644
--- a/x-pack/plugins/search_indices/public/components/indices/indices_router.tsx
+++ b/x-pack/plugins/search_indices/public/components/indices_router.tsx
@@ -7,13 +7,17 @@
import React from 'react';
import { Route, Router, Routes } from '@kbn/shared-ux-router';
import { Redirect } from 'react-router-dom';
-import { useKibana } from '../../hooks/use_kibana';
+
+import { useKibana } from '../hooks/use_kibana';
import {
SearchIndexDetailsTabs,
SEARCH_INDICES_DETAILS_PATH,
SEARCH_INDICES_DETAILS_TABS_PATH,
-} from '../../routes';
-import { SearchIndexDetailsPage } from './details_page';
+ CREATE_INDEX_PATH,
+} from '../routes';
+import { SearchIndexDetailsPage } from './indices/details_page';
+import { CreateIndexPage } from './create_index/create_index_page';
+
export const SearchIndicesRouter: React.FC = () => {
const { application, history } = useKibana().services;
return (
@@ -29,6 +33,7 @@ export const SearchIndicesRouter: React.FC = () => {
/>
+
{
application.navigateToApp('elasticsearchStart');
diff --git a/x-pack/plugins/search_indices/public/components/start/api_key_callout.tsx b/x-pack/plugins/search_indices/public/components/shared/api_key_callout.tsx
similarity index 82%
rename from x-pack/plugins/search_indices/public/components/start/api_key_callout.tsx
rename to x-pack/plugins/search_indices/public/components/shared/api_key_callout.tsx
index 65363e9f73225..1fe6c6d1a7ed7 100644
--- a/x-pack/plugins/search_indices/public/components/start/api_key_callout.tsx
+++ b/x-pack/plugins/search_indices/public/components/shared/api_key_callout.tsx
@@ -17,19 +17,19 @@ interface APIKeyCalloutProps {
export const APIKeyCallout = ({ apiKey }: APIKeyCalloutProps) => {
const title = apiKey
- ? i18n.translate('xpack.searchIndices.startPage.codeView.apiKeyTitle', {
+ ? i18n.translate('xpack.searchIndices.shared.codeView.apiKeyTitle', {
defaultMessage: 'Copy your API key',
})
- : i18n.translate('xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle', {
+ : i18n.translate('xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle', {
defaultMessage: 'Create an API key',
});
const description = apiKey
- ? i18n.translate('xpack.searchIndices.startPage.codeView.apiKeyDescription', {
+ ? i18n.translate('xpack.searchIndices.shared.codeView.apiKeyDescription', {
defaultMessage:
'Make sure you keep it somewhere safe. You won’t be able to retrieve it later.',
})
- : i18n.translate('xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription', {
+ : i18n.translate('xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription', {
defaultMessage: 'Create an API key to connect to Elasticsearch.',
});
diff --git a/x-pack/plugins/search_indices/public/components/shared/breadcrumbs.ts b/x-pack/plugins/search_indices/public/components/shared/breadcrumbs.ts
new file mode 100644
index 0000000000000..2805100d6cabb
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/shared/breadcrumbs.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
+import { i18n } from '@kbn/i18n';
+
+export const IndexManagementBreadcrumbs: ChromeBreadcrumb[] = [
+ {
+ text: i18n.translate('xpack.searchIndices.breadcrumbs.indexManagement.label', {
+ defaultMessage: 'Index Management',
+ }),
+ href: '/app/management/data/index_management',
+ },
+ {
+ text: i18n.translate('xpack.searchIndices.breadcrumbs.indexManagement.indices.label', {
+ defaultMessage: 'Indices',
+ }),
+ href: '/app/management/data/index_management/indices',
+ },
+];
diff --git a/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx b/x-pack/plugins/search_indices/public/components/shared/create_index_code_view.tsx
similarity index 67%
rename from x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
rename to x-pack/plugins/search_indices/public/components/shared/create_index_code_view.tsx
index fadfe1c7dcb90..14e9162fb9706 100644
--- a/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
+++ b/x-pack/plugins/search_indices/public/components/shared/create_index_code_view.tsx
@@ -4,61 +4,55 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React, { useCallback, useMemo } from 'react';
+import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { TryInConsoleButton } from '@kbn/try-in-console';
import { useSearchApiKey } from '@kbn/search-api-keys-components';
-import { AnalyticsEvents } from '../../analytics/constants';
import { Languages, AvailableLanguages, LanguageOptions } from '../../code_examples';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { useKibana } from '../../hooks/use_kibana';
import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
-import { CodeSample } from '../shared/code_sample';
-import { LanguageSelector } from '../shared/language_selector';
-
-import { CreateIndexFormState } from './types';
-import { useStartPageCodingExamples } from './hooks/use_coding_examples';
import { APIKeyCallout } from './api_key_callout';
+import { CodeSample } from './code_sample';
+import { useCreateIndexCodingExamples } from './hooks/use_create_index_coding_examples';
+import { LanguageSelector } from './language_selector';
export interface CreateIndexCodeViewProps {
- createIndexForm: CreateIndexFormState;
+ selectedLanguage: AvailableLanguages;
+ indexName: string;
changeCodingLanguage: (language: AvailableLanguages) => void;
canCreateApiKey?: boolean;
+ analyticsEvents: {
+ runInConsole: string;
+ installCommands: string;
+ createIndex: string;
+ };
}
export const CreateIndexCodeView = ({
- createIndexForm,
- changeCodingLanguage,
+ analyticsEvents,
canCreateApiKey,
+ changeCodingLanguage,
+ indexName,
+ selectedLanguage,
}: CreateIndexCodeViewProps) => {
const { application, share, console: consolePlugin } = useKibana().services;
const usageTracker = useUsageTracker();
- const selectedCodeExamples = useStartPageCodingExamples();
+ const selectedCodeExamples = useCreateIndexCodingExamples();
- const { codingLanguage: selectedLanguage } = createIndexForm;
- const onSelectLanguage = useCallback(
- (value: AvailableLanguages) => {
- changeCodingLanguage(value);
- usageTracker.count([
- AnalyticsEvents.startCreateIndexLanguageSelect,
- `${AnalyticsEvents.startCreateIndexLanguageSelect}_${value}`,
- ]);
- },
- [usageTracker, changeCodingLanguage]
- );
const elasticsearchUrl = useElasticsearchUrl();
const { apiKey, apiKeyIsVisible } = useSearchApiKey();
const codeParams = useMemo(() => {
return {
- indexName: createIndexForm.indexName || undefined,
+ indexName: indexName || undefined,
elasticsearchURL: elasticsearchUrl,
apiKey: apiKeyIsVisible && apiKey ? apiKey : undefined,
};
- }, [createIndexForm.indexName, elasticsearchUrl, apiKeyIsVisible, apiKey]);
+ }, [indexName, elasticsearchUrl, apiKeyIsVisible, apiKey]);
const selectedCodeExample = useMemo(() => {
return selectedCodeExamples[selectedLanguage];
}, [selectedLanguage, selectedCodeExamples]);
@@ -75,7 +69,7 @@ export const CreateIndexCodeView = ({
@@ -87,8 +81,8 @@ export const CreateIndexCodeView = ({
telemetryId={`${selectedLanguage}_create_index`}
onClick={() => {
usageTracker.click([
- AnalyticsEvents.startCreateIndexRunInConsole,
- `${AnalyticsEvents.startCreateIndexRunInConsole}_${selectedLanguage}`,
+ analyticsEvents.runInConsole,
+ `${analyticsEvents.runInConsole}_${selectedLanguage}`,
]);
}}
/>
@@ -102,8 +96,8 @@ export const CreateIndexCodeView = ({
code={selectedCodeExample.installCommand}
onCodeCopyClick={() => {
usageTracker.click([
- AnalyticsEvents.startCreateIndexCodeCopyInstall,
- `${AnalyticsEvents.startCreateIndexCodeCopyInstall}_${selectedLanguage}`,
+ analyticsEvents.installCommands,
+ `${analyticsEvents.installCommands}_${selectedLanguage}`,
]);
}}
/>
@@ -116,9 +110,9 @@ export const CreateIndexCodeView = ({
code={selectedCodeExample.createIndex(codeParams)}
onCodeCopyClick={() => {
usageTracker.click([
- AnalyticsEvents.startCreateIndexCodeCopy,
- `${AnalyticsEvents.startCreateIndexCodeCopy}_${selectedLanguage}`,
- `${AnalyticsEvents.startCreateIndexCodeCopy}_${selectedLanguage}_${selectedCodeExamples.exampleType}`,
+ analyticsEvents.createIndex,
+ `${analyticsEvents.createIndex}_${selectedLanguage}`,
+ `${analyticsEvents.createIndex}_${selectedLanguage}_${selectedCodeExamples.exampleType}`,
]);
}}
/>
diff --git a/x-pack/plugins/search_indices/public/components/shared/create_index_form.tsx b/x-pack/plugins/search_indices/public/components/shared/create_index_form.tsx
new file mode 100644
index 0000000000000..ba2f83cb273da
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/shared/create_index_form.tsx
@@ -0,0 +1,165 @@
+/*
+ * 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 {
+ EuiButton,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiForm,
+ EuiFormRow,
+ EuiHorizontalRule,
+ EuiIcon,
+ EuiLink,
+ EuiPanel,
+ EuiSpacer,
+ EuiText,
+ EuiToolTip,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+import type { UserStartPrivilegesResponse } from '../../../common';
+
+export interface CreateIndexFormProps {
+ indexName: string;
+ indexNameHasError: boolean;
+ isLoading: boolean;
+ onCreateIndex: (e: React.FormEvent) => void;
+ onFileUpload: () => void;
+ onIndexNameChange: (e: React.ChangeEvent) => void;
+ showAPIKeyCreateLabel: boolean;
+ userPrivileges?: UserStartPrivilegesResponse;
+}
+
+export const CreateIndexForm = ({
+ indexName,
+ indexNameHasError,
+ isLoading,
+ onCreateIndex,
+ onFileUpload,
+ onIndexNameChange,
+ showAPIKeyCreateLabel,
+ userPrivileges,
+}: CreateIndexFormProps) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.permissionTooltip', {
+ defaultMessage: 'You do not have permission to create an index.',
+ })}
+
+ ) : undefined
+ }
+ >
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.action.text', {
+ defaultMessage: 'Create my index',
+ })}
+
+
+
+
+ {showAPIKeyCreateLabel && (
+
+
+
+
+ {i18n.translate(
+ 'xpack.searchIndices.shared.createIndex.apiKeyCreation.description',
+ {
+ defaultMessage: "We'll create an API key for this index",
+ }
+ )}
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.fileUpload.link', {
+ defaultMessage: 'Upload a file',
+ })}
+
+ ),
+ }}
+ />
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/shared/create_index_panel.tsx b/x-pack/plugins/search_indices/public/components/shared/create_index_panel.tsx
new file mode 100644
index 0000000000000..8c353ebab6bd8
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/shared/create_index_panel.tsx
@@ -0,0 +1,271 @@
+/*
+ * 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, { useMemo } from 'react';
+import {
+ EuiButtonEmpty,
+ EuiButtonGroup,
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiPanel,
+ EuiSpacer,
+ EuiText,
+ EuiTextAlign,
+ EuiTitle,
+ useEuiTheme,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { docLinks } from '../../../common/doc_links';
+import { useKibana } from '../../hooks/use_kibana';
+import { CreateIndexViewMode } from '../../types';
+
+const MAX_WIDTH = '650px';
+
+export interface CreateIndexPanelProps {
+ children: React.ReactNode | React.ReactNode[];
+ createIndexView: CreateIndexViewMode;
+ onChangeView: (id: string) => void;
+ onClose: () => void;
+ showCallouts?: boolean;
+ showSkip?: boolean;
+ title?: React.ReactNode;
+}
+
+export const CreateIndexPanel = ({
+ children,
+ createIndexView,
+ onChangeView,
+ onClose,
+ showCallouts,
+ showSkip,
+ title,
+}: CreateIndexPanelProps) => {
+ const { cloud, http } = useKibana().services;
+ const { euiTheme } = useEuiTheme();
+
+ const o11yTrialLink = useMemo(() => {
+ if (cloud && cloud.isServerlessEnabled) {
+ const baseUrl = cloud?.projectsUrl ?? 'https://cloud.elastic.co/projects/';
+ return `${baseUrl}create/observability/start`;
+ }
+ return http.basePath.prepend('/app/observability/onboarding');
+ }, [cloud, http]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.pageTitle', {
+ defaultMessage: 'Elasticsearch',
+ })}
+
+
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.pageDescription', {
+ defaultMessage: 'Get started with Elasticsearch',
+ })}
+
+
+
+
+
+
+
+
+
+
+ {title ??
+ i18n.translate('xpack.searchIndices.shared.createIndex.defaultTitle', {
+ defaultMessage: 'Create an index',
+ })}
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.description', {
+ defaultMessage:
+ 'An index stores your data and defines the schema, or field mappings, for your searches',
+ })}
+
+
+ {children}
+
+
+ {showCallouts && (
+ <>
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.searchIndices.shared.createIndex.observabilityCallout.title',
+ {
+ defaultMessage: 'Looking to store your logs or metrics data?',
+ }
+ )}
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button',
+ {
+ defaultMessage: 'Collect and analyze logs',
+ }
+ )}
+
+
+
+ {i18n.translate(
+ 'xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle',
+ {
+ defaultMessage: 'Explore Logstash and Beats',
+ }
+ )}
+
+
+
+
+ or
+
+
+
+ {i18n.translate(
+ 'xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button',
+ {
+ defaultMessage: 'Start an Observability trial',
+ }
+ )}
+
+
+
+ {i18n.translate(
+ 'xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle',
+ {
+ defaultMessage: 'Powerful performance monitoring',
+ }
+ )}
+
+
+
+
+
+ >
+ )}
+
+ {showSkip === true && (
+ <>
+
+
+
+ {i18n.translate('xpack.searchIndices.shared.createIndex.skipLabel', {
+ defaultMessage: 'Skip',
+ })}
+
+
+ >
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/start/hooks/use_create_index.tsx b/x-pack/plugins/search_indices/public/components/shared/hooks/use_create_index.tsx
similarity index 94%
rename from x-pack/plugins/search_indices/public/components/start/hooks/use_create_index.tsx
rename to x-pack/plugins/search_indices/public/components/shared/hooks/use_create_index.tsx
index 8daafec5573ca..537aa3cc4b987 100644
--- a/x-pack/plugins/search_indices/public/components/start/hooks/use_create_index.tsx
+++ b/x-pack/plugins/search_indices/public/components/shared/hooks/use_create_index.tsx
@@ -11,7 +11,7 @@ import { useCreateIndex as useCreateIndexApi } from '../../../hooks/api/use_crea
import { useKibana } from '../../../hooks/use_kibana';
-import { navigateToIndexDetails } from './utils';
+import { navigateToIndexDetails } from '../../utils';
export const useCreateIndex = () => {
const { application, http } = useKibana().services;
diff --git a/x-pack/plugins/search_indices/public/components/start/hooks/use_coding_examples.tsx b/x-pack/plugins/search_indices/public/components/shared/hooks/use_create_index_coding_examples.tsx
similarity index 87%
rename from x-pack/plugins/search_indices/public/components/start/hooks/use_coding_examples.tsx
rename to x-pack/plugins/search_indices/public/components/shared/hooks/use_create_index_coding_examples.tsx
index 1a351d10943f2..fb1cb6a7eab52 100644
--- a/x-pack/plugins/search_indices/public/components/start/hooks/use_coding_examples.tsx
+++ b/x-pack/plugins/search_indices/public/components/shared/hooks/use_create_index_coding_examples.tsx
@@ -8,7 +8,7 @@
import { CreateIndexCodeExamples } from '../../../types';
import { DenseVectorSeverlessCodeExamples } from '../../../code_examples/create_index';
-export const useStartPageCodingExamples = (): CreateIndexCodeExamples => {
+export const useCreateIndexCodingExamples = (): CreateIndexCodeExamples => {
// TODO: in the future this will be dynamic based on the onboarding token
// or project sub-type
return DenseVectorSeverlessCodeExamples;
diff --git a/x-pack/plugins/search_indices/public/components/start/status_error.tsx b/x-pack/plugins/search_indices/public/components/shared/load_indices_status_error.tsx
similarity index 79%
rename from x-pack/plugins/search_indices/public/components/start/status_error.tsx
rename to x-pack/plugins/search_indices/public/components/shared/load_indices_status_error.tsx
index 7e41e37d5cd94..58e1867cc577d 100644
--- a/x-pack/plugins/search_indices/public/components/start/status_error.tsx
+++ b/x-pack/plugins/search_indices/public/components/shared/load_indices_status_error.tsx
@@ -15,14 +15,14 @@ export interface StartPageErrorProps {
error: unknown;
}
-export const StartPageError = ({ error }: StartPageErrorProps) => {
+export const LoadIndicesStatusError = ({ error }: StartPageErrorProps) => {
return (
- {i18n.translate('xpack.searchIndices.startPage.statusFetchError.title', {
+ {i18n.translate('xpack.searchIndices.shared.statusFetchError.title', {
defaultMessage: 'Error loading indices',
})}
@@ -31,7 +31,7 @@ export const StartPageError = ({ error }: StartPageErrorProps) => {
{getErrorMessage(
error,
- i18n.translate('xpack.searchIndices.startPage.statusFetchError.unknownError', {
+ i18n.translate('xpack.searchIndices.shared.statusFetchError.unknownError', {
defaultMessage: 'Unknown error fetching indices.',
})
)}
diff --git a/x-pack/plugins/search_indices/public/components/start/create_index.tsx b/x-pack/plugins/search_indices/public/components/start/create_index.tsx
index 788bd1e36f2ee..e7ca978fb8eaa 100644
--- a/x-pack/plugins/search_indices/public/components/start/create_index.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/create_index.tsx
@@ -6,52 +6,29 @@
*/
import React, { useCallback, useState } from 'react';
-import {
- EuiButton,
- EuiFieldText,
- EuiFlexGroup,
- EuiFlexItem,
- EuiForm,
- EuiFormRow,
- EuiHorizontalRule,
- EuiIcon,
- EuiLink,
- EuiPanel,
- EuiSpacer,
- EuiText,
- EuiToolTip,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n-react';
import type { UserStartPrivilegesResponse } from '../../../common';
import { AnalyticsEvents } from '../../analytics/constants';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
+import { CreateIndexFormState } from '../../types';
import { isValidIndexName } from '../../utils/indices';
-import { useCreateIndex } from './hooks/use_create_index';
+import { useCreateIndex } from '../shared/hooks/use_create_index';
+import { CreateIndexForm } from '../shared/create_index_form';
-import { CreateIndexFormState } from './types';
import { useKibana } from '../../hooks/use_kibana';
-const CREATE_INDEX_CONTENT = i18n.translate(
- 'xpack.searchIndices.startPage.createIndex.action.text',
- {
- defaultMessage: 'Create my index',
- }
-);
-
-export interface CreateIndexFormProps {
+export interface CreateIndexUIViewProps {
formState: CreateIndexFormState;
setFormState: React.Dispatch>;
userPrivileges?: UserStartPrivilegesResponse;
}
-export const CreateIndexForm = ({
+export const CreateIndexUIView = ({
userPrivileges,
formState,
setFormState,
-}: CreateIndexFormProps) => {
+}: CreateIndexUIViewProps) => {
const { application } = useKibana().services;
const [indexNameHasError, setIndexNameHasError] = useState(false);
const usageTracker = useUsageTracker();
@@ -86,129 +63,15 @@ export const CreateIndexForm = ({
}, [usageTracker, application]);
return (
- <>
-
-
-
-
-
-
-
- {userPrivileges?.privileges?.canCreateIndex === false ? (
-
- {i18n.translate('xpack.searchIndices.startPage.createIndex.permissionTooltip', {
- defaultMessage: 'You do not have permission to create an index.',
- })}
-
- }
- >
-
- {CREATE_INDEX_CONTENT}
-
-
- ) : (
-
- {CREATE_INDEX_CONTENT}
-
- )}
-
-
- {userPrivileges?.privileges?.canCreateApiKeys && (
-
-
-
-
- {i18n.translate(
- 'xpack.searchIndices.startPage.createIndex.apiKeyCreation.description',
- {
- defaultMessage: "We'll create an API key for this index",
- }
- )}
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {i18n.translate(
- 'xpack.searchIndices.startPage.createIndex.fileUpload.link',
- {
- defaultMessage: 'Upload a file',
- }
- )}
-
- ),
- }}
- />
-
-
-
-
-
- >
+
);
};
diff --git a/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx b/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx
index 42b021043cb34..3f3063ddb150e 100644
--- a/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx
@@ -5,23 +5,10 @@
* 2.0.
*/
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import {
- EuiButtonEmpty,
- EuiButtonGroup,
- EuiFlexGroup,
- EuiFlexItem,
- EuiIcon,
- EuiPanel,
- EuiSpacer,
- EuiText,
- EuiTextAlign,
- EuiTitle,
-} from '@elastic/eui';
+import React, { useCallback, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import type { IndicesStatusResponse, UserStartPrivilegesResponse } from '../../../common';
-import { docLinks } from '../../../common/doc_links';
import { AnalyticsEvents } from '../../analytics/constants';
import { AvailableLanguages } from '../../code_examples';
@@ -29,9 +16,11 @@ import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { generateRandomIndexName } from '../../utils/indices';
import { getDefaultCodingLanguage } from '../../utils/language';
-import { CreateIndexForm } from './create_index';
-import { CreateIndexCodeView } from './create_index_code';
-import { CreateIndexFormState } from './types';
+import { CreateIndexUIView } from './create_index';
+import { CreateIndexCodeView } from '../shared/create_index_code_view';
+import { CreateIndexFormState, CreateIndexViewMode } from '../../types';
+
+import { CreateIndexPanel } from '../shared/create_index_panel';
import { useKibana } from '../../hooks/use_kibana';
function initCreateIndexState(): CreateIndexFormState {
@@ -43,21 +32,17 @@ function initCreateIndexState(): CreateIndexFormState {
};
}
-const MAX_WIDTH = '650px';
-
-enum CreateIndexView {
- UI = 'ui',
- Code = 'code',
-}
export interface ElasticsearchStartProps {
indicesData?: IndicesStatusResponse;
userPrivileges?: UserStartPrivilegesResponse;
}
export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps) => {
- const { cloud, http } = useKibana().services;
- const [createIndexView, setCreateIndexView] = useState(
- userPrivileges?.privileges.canCreateIndex === false ? CreateIndexView.Code : CreateIndexView.UI
+ const { application } = useKibana().services;
+ const [createIndexView, setCreateIndexViewMode] = useState(
+ userPrivileges?.privileges.canCreateIndex === false
+ ? CreateIndexViewMode.Code
+ : CreateIndexViewMode.UI
);
const [formState, setFormState] = useState(initCreateIndexState);
const usageTracker = useUsageTracker();
@@ -68,28 +53,20 @@ export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps)
useEffect(() => {
if (userPrivileges === undefined) return;
if (userPrivileges.privileges.canCreateIndex === false) {
- setCreateIndexView(CreateIndexView.Code);
+ setCreateIndexViewMode(CreateIndexViewMode.Code);
}
}, [userPrivileges]);
- const o11yTrialLink = useMemo(() => {
- if (cloud && cloud.isServerlessEnabled) {
- const baseUrl = cloud?.projectsUrl ?? 'https://cloud.elastic.co/projects/';
- return `${baseUrl}create/observability/start`;
- }
- return http.basePath.prepend('/app/observability/onboarding');
- }, [cloud, http]);
-
const onChangeView = useCallback(
(id: string) => {
switch (id) {
- case CreateIndexView.UI:
+ case CreateIndexViewMode.UI:
usageTracker.click(AnalyticsEvents.startPageShowCreateIndexUIClick);
- setCreateIndexView(CreateIndexView.UI);
+ setCreateIndexViewMode(CreateIndexViewMode.UI);
return;
- case CreateIndexView.Code:
+ case CreateIndexViewMode.Code:
usageTracker.click(AnalyticsEvents.startPageShowCodeClick);
- setCreateIndexView(CreateIndexView.Code);
+ setCreateIndexViewMode(CreateIndexViewMode.Code);
return;
}
},
@@ -101,178 +78,48 @@ export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps)
...formState,
codingLanguage: language,
});
+ usageTracker.count([
+ AnalyticsEvents.startCreateIndexLanguageSelect,
+ `${AnalyticsEvents.startCreateIndexLanguageSelect}_${language}`,
+ ]);
},
- [formState, setFormState]
+ [usageTracker, formState, setFormState]
);
+ const onClose = useCallback(() => {
+ application.navigateToApp('management', { deepLinkId: 'index_management' });
+ }, [application]);
return (
-
-
-
-
-
-
-
-
-
- {i18n.translate('xpack.searchIndices.startPage.pageTitle', {
- defaultMessage: 'Elasticsearch',
- })}
-
-
-
-
-
-
-
- {i18n.translate('xpack.searchIndices.startPage.pageDescription', {
- defaultMessage: 'Vectorize, search, and visualize your data',
- })}
-
-
-
-
-
-
-
-
-
-
- {i18n.translate('xpack.searchIndices.startPage.createIndex.title', {
- defaultMessage: 'Create your first index',
- })}
-
-
-
-
-
-
-
-
-
- {i18n.translate('xpack.searchIndices.startPage.createIndex.description', {
- defaultMessage:
- 'An index stores your data and defines the schema, or field mappings, for your searches',
- })}
-
-
- {createIndexView === CreateIndexView.UI && (
-
- )}
- {createIndexView === CreateIndexView.Code && (
-
- )}
-
-
-
-
-
-
-
- {i18n.translate('xpack.searchIndices.startPage.observabilityCallout.title', {
- defaultMessage: 'Looking to store your logs or metrics data?',
- })}
-
-
-
-
-
-
-
- {i18n.translate('xpack.searchIndices.startPage.observabilityCallout.logs.button', {
- defaultMessage: 'Collect and analyze logs',
- })}
-
-
-
- {i18n.translate(
- 'xpack.searchIndices.startPage.observabilityCallout.logs.subTitle',
- {
- defaultMessage: 'Explore Logstash and Beats',
- }
- )}
-
-
-
-
- or
-
-
-
- {i18n.translate(
- 'xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button',
- {
- defaultMessage: 'Start an Observability trial',
- }
- )}
-
-
-
- {i18n.translate(
- 'xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle',
- {
- defaultMessage: 'Powerful performance monitoring',
- }
- )}
-
-
-
-
-
-
+ {createIndexView === CreateIndexViewMode.UI && (
+
+ )}
+ {createIndexView === CreateIndexViewMode.Code && (
+
+ )}
+
);
};
diff --git a/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx b/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx
index 899d44da2afa8..6909b1117e327 100644
--- a/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx
@@ -11,7 +11,7 @@ import type { IndicesStatusResponse } from '../../../../common';
import { useKibana } from '../../../hooks/use_kibana';
-import { navigateToIndexDetails } from './utils';
+import { navigateToIndexDetails } from '../../utils';
import { useUsageTracker } from '../../../contexts/usage_tracker_context';
import { AnalyticsEvents } from '../../../analytics/constants';
diff --git a/x-pack/plugins/search_indices/public/components/start/start_page.tsx b/x-pack/plugins/search_indices/public/components/start/start_page.tsx
index 4a848f580d22f..4dabec2e5fa98 100644
--- a/x-pack/plugins/search_indices/public/components/start/start_page.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/start_page.tsx
@@ -6,6 +6,7 @@
*/
import React, { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
import { EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
@@ -16,7 +17,13 @@ import { useUserPrivilegesQuery } from '../../hooks/api/use_user_permissions';
import { useIndicesRedirect } from './hooks/use_indices_redirect';
import { ElasticsearchStart } from './elasticsearch_start';
-import { StartPageError } from './status_error';
+import { LoadIndicesStatusError } from '../shared/load_indices_status_error';
+import { IndexManagementBreadcrumbs } from '../shared/breadcrumbs';
+import { usePageChrome } from '../../hooks/use_page_chrome';
+
+const PageTitle = i18n.translate('xpack.searchIndices.startPage.docTitle', {
+ defaultMessage: 'Create your first index',
+});
export const ElasticsearchStartPage = () => {
const { console: consolePlugin } = useKibana().services;
@@ -27,6 +34,7 @@ export const ElasticsearchStartPage = () => {
error: indicesFetchError,
} = useIndicesStatusQuery();
const { data: userPrivileges } = useUserPrivilegesQuery();
+ usePageChrome(PageTitle, [...IndexManagementBreadcrumbs, { text: PageTitle }]);
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? : null),
@@ -43,7 +51,7 @@ export const ElasticsearchStartPage = () => {
>
{isInitialLoading && }
- {hasIndicesStatusFetchError && }
+ {hasIndicesStatusFetchError && }
{!isInitialLoading && !hasIndicesStatusFetchError && (
)}
diff --git a/x-pack/plugins/search_indices/public/components/start/types.ts b/x-pack/plugins/search_indices/public/components/start/types.ts
deleted file mode 100644
index 4c0235ec515f1..0000000000000
--- a/x-pack/plugins/search_indices/public/components/start/types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { AvailableLanguages } from '../../code_examples';
-
-export interface CreateIndexFormState {
- indexName: string;
- defaultIndexName: string;
- codingLanguage: AvailableLanguages;
-}
diff --git a/x-pack/plugins/search_indices/public/components/start/hooks/utils.ts b/x-pack/plugins/search_indices/public/components/utils.ts
similarity index 66%
rename from x-pack/plugins/search_indices/public/components/start/hooks/utils.ts
rename to x-pack/plugins/search_indices/public/components/utils.ts
index ed8b6f40e51fd..235c03b9faab8 100644
--- a/x-pack/plugins/search_indices/public/components/start/hooks/utils.ts
+++ b/x-pack/plugins/search_indices/public/components/utils.ts
@@ -5,12 +5,15 @@
* 2.0.
*/
+import { generatePath } from 'react-router-dom';
+
import type { ApplicationStart, HttpSetup } from '@kbn/core/public';
+import { INDICES_APP_BASE, SEARCH_INDICES_DETAILS_PATH } from '../routes';
-const INDEX_DETAILS_PATH = '/app/elasticsearch/indices/index_details';
+const INDEX_DETAILS_FULL_PATH = `${INDICES_APP_BASE}${SEARCH_INDICES_DETAILS_PATH}`;
function getIndexDetailsPath(http: HttpSetup, indexName: string) {
- return http.basePath.prepend(`${INDEX_DETAILS_PATH}/${encodeURIComponent(indexName)}`);
+ return http.basePath.prepend(generatePath(INDEX_DETAILS_FULL_PATH, { indexName }));
}
export const navigateToIndexDetails = (
diff --git a/x-pack/plugins/search_indices/public/hooks/use_page_chrome.ts b/x-pack/plugins/search_indices/public/hooks/use_page_chrome.ts
new file mode 100644
index 0000000000000..fae438b502a08
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/hooks/use_page_chrome.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useEffect } from 'react';
+import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
+import { useKibana } from './use_kibana';
+
+export const usePageChrome = (docTitle: string, breadcrumbs: ChromeBreadcrumb[]) => {
+ const { chrome, http, serverless } = useKibana().services;
+
+ useEffect(() => {
+ chrome.docTitle.change(docTitle);
+ const newBreadcrumbs = breadcrumbs.map((breadcrumb) => {
+ if (breadcrumb.href && http.basePath.get().length > 0) {
+ breadcrumb.href = http.basePath.prepend(breadcrumb.href);
+ }
+ return breadcrumb;
+ });
+ if (serverless) {
+ serverless.setBreadcrumbs(newBreadcrumbs);
+ } else {
+ chrome.setBreadcrumbs(newBreadcrumbs);
+ }
+ return () => {
+ // clear manually set breadcrumbs
+ if (serverless) {
+ serverless.setBreadcrumbs([]);
+ } else {
+ chrome.setBreadcrumbs([]);
+ }
+ };
+ }, [breadcrumbs, chrome, docTitle, http.basePath, serverless]);
+};
diff --git a/x-pack/plugins/search_indices/public/locators.ts b/x-pack/plugins/search_indices/public/locators.ts
new file mode 100644
index 0000000000000..587ce51f2c82d
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/locators.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { LocatorDefinition } from '@kbn/share-plugin/common';
+import type { SharePluginSetup } from '@kbn/share-plugin/public';
+import type { SerializableRecord } from '@kbn/utility-types';
+
+import { INDICES_APP_ID } from '../common';
+import { CREATE_INDEX_PATH } from './routes';
+
+export function registerLocators(share: SharePluginSetup) {
+ share.url.locators.create(new CreateIndexLocatorDefinition());
+}
+
+class CreateIndexLocatorDefinition implements LocatorDefinition {
+ public readonly getLocation = async () => {
+ return {
+ app: INDICES_APP_ID,
+ path: CREATE_INDEX_PATH,
+ state: {},
+ };
+ };
+
+ public readonly id = 'SEARCH_CREATE_INDEX';
+}
diff --git a/x-pack/plugins/search_indices/public/plugin.ts b/x-pack/plugins/search_indices/public/plugin.ts
index c9b5c8f4c7659..b92fbaa5e7f45 100644
--- a/x-pack/plugins/search_indices/public/plugin.ts
+++ b/x-pack/plugins/search_indices/public/plugin.ts
@@ -6,10 +6,12 @@
*/
import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
+import { SEARCH_INDICES_CREATE_INDEX } from '@kbn/deeplinks-search/constants';
import { i18n } from '@kbn/i18n';
import { docLinks } from '../common/doc_links';
import type {
+ AppPluginSetupDependencies,
SearchIndicesAppPluginStartDependencies,
SearchIndicesPluginSetup,
SearchIndicesPluginStart,
@@ -17,7 +19,13 @@ import type {
} from './types';
import { initQueryClient } from './services/query_client';
import { INDICES_APP_ID, START_APP_ID } from '../common';
-import { INDICES_APP_BASE, START_APP_BASE, SearchIndexDetailsTabValues } from './routes';
+import {
+ CREATE_INDEX_PATH,
+ INDICES_APP_BASE,
+ START_APP_BASE,
+ SearchIndexDetailsTabValues,
+} from './routes';
+import { registerLocators } from './locators';
export class SearchIndicesPlugin
implements Plugin
@@ -25,7 +33,8 @@ export class SearchIndicesPlugin
private pluginEnabled: boolean = false;
public setup(
- core: CoreSetup
+ core: CoreSetup,
+ plugins: AppPluginSetupDependencies
): SearchIndicesPluginSetup {
this.pluginEnabled = true;
@@ -51,12 +60,21 @@ export class SearchIndicesPlugin
core.application.register({
id: INDICES_APP_ID,
appRoute: INDICES_APP_BASE,
+ deepLinks: [
+ {
+ id: SEARCH_INDICES_CREATE_INDEX,
+ path: CREATE_INDEX_PATH,
+ title: i18n.translate('xpack.searchIndices.elasticsearchIndices.createIndexTitle', {
+ defaultMessage: 'Create index',
+ }),
+ },
+ ],
title: i18n.translate('xpack.searchIndices.elasticsearchIndices.startAppTitle', {
defaultMessage: 'Elasticsearch Indices',
}),
async mount({ element, history }) {
const { renderApp } = await import('./application');
- const { SearchIndicesRouter } = await import('./components/indices/indices_router');
+ const { SearchIndicesRouter } = await import('./components/indices_router');
const [coreStart, depsStart] = await core.getStartServices();
const startDeps: SearchIndicesServicesContextDeps = {
...depsStart,
@@ -66,6 +84,8 @@ export class SearchIndicesPlugin
},
});
+ registerLocators(plugins.share);
+
return {
enabled: true,
startAppId: START_APP_ID,
diff --git a/x-pack/plugins/search_indices/public/routes.ts b/x-pack/plugins/search_indices/public/routes.ts
index 057891d63226d..86d05fb73032d 100644
--- a/x-pack/plugins/search_indices/public/routes.ts
+++ b/x-pack/plugins/search_indices/public/routes.ts
@@ -13,6 +13,7 @@ export enum SearchIndexDetailsTabs {
MAPPINGS = 'mappings',
SETTINGS = 'settings',
}
+export const CREATE_INDEX_PATH = `${ROOT_PATH}create`;
export const SearchIndexDetailsTabValues: string[] = Object.values(SearchIndexDetailsTabs);
export const START_APP_BASE = '/app/elasticsearch/start';
diff --git a/x-pack/plugins/search_indices/public/types.ts b/x-pack/plugins/search_indices/public/types.ts
index cfc732adff45f..ccf4d56e13a67 100644
--- a/x-pack/plugins/search_indices/public/types.ts
+++ b/x-pack/plugins/search_indices/public/types.ts
@@ -5,19 +5,25 @@
* 2.0.
*/
-import type { CloudStart } from '@kbn/cloud-plugin/public';
-import type { ConsolePluginStart } from '@kbn/console-plugin/public';
+import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
+import type { ConsolePluginSetup, ConsolePluginStart } from '@kbn/console-plugin/public';
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
-import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
-import type { SharePluginStart } from '@kbn/share-plugin/public';
-import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
+import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
+import type {
+ UsageCollectionSetup,
+ UsageCollectionStart,
+} from '@kbn/usage-collection-plugin/public';
import type {
MappingProperty,
MappingPropertyBase,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
-import type { IndexManagementPluginStart } from '@kbn/index-management-shared-types';
+import type {
+ IndexManagementPluginSetup,
+ IndexManagementPluginStart,
+} from '@kbn/index-management-shared-types';
import type { AppDeepLinkId } from '@kbn/core-chrome-browser';
-import { ServerlessPluginStart } from '@kbn/serverless/public';
+import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
+import type { AvailableLanguages } from './code_examples';
export interface SearchIndicesPluginSetup {
enabled: boolean;
@@ -31,14 +37,20 @@ export interface SearchIndicesPluginStart {
startRoute: string;
}
-export interface AppPluginStartDependencies {
- navigation: NavigationPublicPluginStart;
+export interface AppPluginSetupDependencies {
+ console?: ConsolePluginSetup;
+ cloud?: CloudSetup;
+ indexManagement: IndexManagementPluginSetup;
+ share: SharePluginSetup;
+ serverless?: ServerlessPluginSetup;
+ usageCollection?: UsageCollectionSetup;
}
export interface SearchIndicesAppPluginStartDependencies {
console?: ConsolePluginStart;
cloud?: CloudStart;
share: SharePluginStart;
+ serverless?: ServerlessPluginStart;
usageCollection?: UsageCollectionStart;
indexManagement: IndexManagementPluginStart;
}
@@ -50,8 +62,6 @@ export interface SearchIndicesServicesContextDeps {
export type SearchIndicesServicesContext = CoreStart &
SearchIndicesAppPluginStartDependencies & {
history: AppMountParameters['history'];
- indexManagement: IndexManagementPluginStart;
- serverless: ServerlessPluginStart;
};
export interface AppUsageTracker {
@@ -123,3 +133,14 @@ export interface IngestDataCodeExamples {
python: IngestDataCodeDefinition;
javascript: IngestDataCodeDefinition;
}
+
+export interface CreateIndexFormState {
+ indexName: string;
+ defaultIndexName: string;
+ codingLanguage: AvailableLanguages;
+}
+
+export enum CreateIndexViewMode {
+ UI = 'ui',
+ Code = 'code',
+}
diff --git a/x-pack/plugins/search_indices/public/utils/indices.test.ts b/x-pack/plugins/search_indices/public/utils/indices.test.ts
index 8ddd7cbb56fc5..a3b6654a1209e 100644
--- a/x-pack/plugins/search_indices/public/utils/indices.test.ts
+++ b/x-pack/plugins/search_indices/public/utils/indices.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { generateRandomIndexName, isValidIndexName } from './indices';
+import { generateRandomIndexName, isValidIndexName, getFirstNewIndexName } from './indices';
describe('indices utils', function () {
describe('generateRandomIndexName', function () {
@@ -46,4 +46,31 @@ describe('indices utils', function () {
expect(isValidIndexName(indexName)).toBe(true);
});
});
+
+ describe('getFirstNewIndexName', function () {
+ it('returns undefined when lists are the same', () => {
+ expect(getFirstNewIndexName([], [])).toEqual(undefined);
+ expect(getFirstNewIndexName(['index'], ['index'])).toEqual(undefined);
+ expect(getFirstNewIndexName(['index', 'test'], ['index', 'test'])).toEqual(undefined);
+ });
+
+ it('returns new item when it exists', () => {
+ expect(getFirstNewIndexName([], ['index'])).toEqual('index');
+ expect(getFirstNewIndexName(['index'], ['index', 'test'])).toEqual('test');
+ expect(getFirstNewIndexName(['index', 'test'], ['index', 'test', 'unit-test'])).toEqual(
+ 'unit-test'
+ );
+ expect(getFirstNewIndexName(['index', 'test'], ['unit-test', 'index', 'test'])).toEqual(
+ 'unit-test'
+ );
+ });
+ it('returns first new item when it multiple new indices exists', () => {
+ expect(getFirstNewIndexName([], ['index', 'test'])).toEqual('index');
+ expect(getFirstNewIndexName(['index'], ['test', 'index', 'unit-test'])).toEqual('test');
+ });
+ it('can handle old indices being removed', () => {
+ expect(getFirstNewIndexName(['index'], ['test'])).toEqual('test');
+ expect(getFirstNewIndexName(['test', 'index', 'unit-test'], ['index', 'new'])).toEqual('new');
+ });
+ });
});
diff --git a/x-pack/plugins/search_indices/public/utils/indices.ts b/x-pack/plugins/search_indices/public/utils/indices.ts
index 21c6e672af08f..3812eea8757b9 100644
--- a/x-pack/plugins/search_indices/public/utils/indices.ts
+++ b/x-pack/plugins/search_indices/public/utils/indices.ts
@@ -35,3 +35,12 @@ export function generateRandomIndexName(
return result;
}
+
+export function getFirstNewIndexName(startingIndexNames: string[], currentIndexNames: string[]) {
+ for (const index of currentIndexNames) {
+ if (startingIndexNames.indexOf(index) === -1) {
+ return index;
+ }
+ }
+ return undefined;
+}
diff --git a/x-pack/plugins/search_indices/tsconfig.json b/x-pack/plugins/search_indices/tsconfig.json
index 61b82f4485492..341dd230cee5f 100644
--- a/x-pack/plugins/search_indices/tsconfig.json
+++ b/x-pack/plugins/search_indices/tsconfig.json
@@ -12,7 +12,6 @@
],
"kbn_references": [
"@kbn/core",
- "@kbn/navigation-plugin",
"@kbn/config-schema",
"@kbn/core-elasticsearch-server",
"@kbn/logging",
@@ -39,7 +38,8 @@
"@kbn/search-shared-ui",
"@kbn/deeplinks-search",
"@kbn/core-chrome-browser",
- "@kbn/serverless"
+ "@kbn/serverless",
+ "@kbn/utility-types"
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/search_playground/public/components/setup_page/create_index_button.tsx b/x-pack/plugins/search_playground/public/components/setup_page/create_index_button.tsx
index d4a165f56266c..5bc9f84c833fb 100644
--- a/x-pack/plugins/search_playground/public/components/setup_page/create_index_button.tsx
+++ b/x-pack/plugins/search_playground/public/components/setup_page/create_index_button.tsx
@@ -16,7 +16,9 @@ export const CreateIndexButton: React.FC = () => {
services: { application, share },
} = useKibana();
const createIndexLocator = useMemo(
- () => share.url.locators.get('CREATE_INDEX_LOCATOR_ID'),
+ () =>
+ share.url.locators.get('CREATE_INDEX_LOCATOR_ID') ??
+ share.url.locators.get('SEARCH_CREATE_INDEX'),
[share.url.locators]
);
const handleNavigateToIndex = useCallback(async () => {
diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/exporting.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/exporting.md
new file mode 100644
index 0000000000000..f4cbc66779a81
--- /dev/null
+++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/exporting.md
@@ -0,0 +1,65 @@
+# Prebuilt Rule Export
+
+This is a test plan for the exporting of prebuilt rules. This feature is an aspect of `Milestone 2` of the [Rule Immutability/Customization](https://github.com/elastic/security-team/issues/1974) epic.
+
+Status: `in progress`.
+
+## Useful information
+
+### Tickets
+
+- [Rule Immutability/Customization](https://github.com/elastic/security-team/issues/1974)
+- [Rule Exporting Feature](https://github.com/elastic/kibana/issues/180167#issue-2227974379)
+- [Rule Export API PR](https://github.com/elastic/kibana/pull/194498)
+
+### Terminology
+
+- **prebuilt rule**: A rule contained in our `Prebuilt Security Detection Rules` integration in Fleet.
+- **custom rule**: A rule defined by the user, which has no relation to the prebuilt rules
+- **rule source, or ruleSource**: A field on the rule that defines the rule's categorization
+
+## Scenarios
+
+### Core Functionality
+
+#### Scenario: Exporting prebuilt rule individually
+```Gherkin
+Given a space with prebuilt rules installed
+When the user selects "Export rule" from the "All actions" dropdown on the rule's page
+Then the rule should be exported as an NDJSON file
+And it should include an "immutable" field with a value of true
+And its "ruleSource" "type" should be "external"
+And its "ruleSource" "isCustomized" value should depend on whether the rule was customized
+```
+
+#### Scenario: Exporting prebuilt rules in bulk
+```Gherkin
+Given a space with prebuilt rules installed
+When the user selects prebuilt rules in the alerts table
+And chooses "Export" from bulk actions
+Then the selected rules should be exported as an NDJSON file
+And they should include an "immutable" field with a value of true
+And their "ruleSource" "type" should be "external"
+And their "ruleSource" "isCustomized" should depend on whether the rule was customized
+```
+
+#### Scenario: Exporting both prebuilt and custom rules in bulk
+```Gherkin
+Given a space with prebuilt and custom rules installed
+When the user selects prebuilt rules in the alerts table
+And chooses "Export" from bulk actions
+Then the selected rules should be exported as an NDJSON file
+And the prebuilt rules should include an "immutable" field with a value of true
+And the custom rules should include an "immutable" field with a value of false
+And the prebuilt rules' "ruleSource" "type" should be "external"
+And the custom rules' "ruleSource" "type" should be "internal"
+```
+
+### Error Handling
+
+#### Scenario: Exporting beyond the export limit
+```Gherkin
+Given a space with prebuilt and custom rules installed
+And the number of rules is greater than the export limit (defaults to 10_000)
+Then the request should be rejected as a bad request
+```
diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/importing.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/importing.md
new file mode 100644
index 0000000000000..0c947d0a52b95
--- /dev/null
+++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/importing.md
@@ -0,0 +1,127 @@
+# Prebuilt Rule Import
+
+This is a test plan for the importing of prebuilt rules. This feature is an aspect of `Milestone 2` of the [Rule Immutability/Customization](https://github.com/elastic/security-team/issues/1974) epic.
+
+Status: `in progress`.
+
+## Useful information
+
+### Tickets
+
+- [Rule Immutability/Customization](https://github.com/elastic/security-team/issues/1974)
+- [Rule Importing Feature](https://github.com/elastic/kibana/issues/180168)
+- [Rule Import API PR](https://github.com/elastic/kibana/pull/190198)
+
+### Terminology
+
+- **prebuilt rule**: A rule contained in our `Prebuilt Security Detection Rules` integration in Fleet.
+- **custom rule**: A rule defined by the user, which has no relation to the prebuilt rules
+- **rule source, or ruleSource**: A field on the rule that defines the rule's categorization
+
+## Scenarios
+
+### Core Functionality
+
+#### Scenario: Importing an unmodified prebuilt rule with a matching rule_id and version
+
+```Gherkin
+Given the import payload contains a prebuilt rule with a matching rule_id and version, identical to the published rule
+When the user imports the rule
+Then the rule should be created or updated
+And the ruleSource type should be "external"
+And isCustomized should be false
+```
+
+#### Scenario: Importing a customized prebuilt rule with a matching rule_id and version
+
+```Gherkin
+Given the import payload contains a prebuilt rule with a matching rule_id and version, modified from the published version
+When the user imports the rule
+Then the rule should be created or updated
+And the ruleSource type should be "external"
+And isCustomized should be true
+```
+
+#### Scenario: Importing a prebuilt rule with a matching rule_id but no matching version
+
+```Gherkin
+Given the import payload contains a prebuilt rule with a matching rule_id but no matching version
+When the user imports the rule
+Then the rule should be created or updated
+And the ruleSource type should be "external"
+And isCustomized should be true
+```
+
+#### Scenario: Importing a prebuilt rule with a non-existent rule_id
+
+```Gherkin
+Given the import payload contains a prebuilt rule with a non-existent rule_id
+When the user imports the rule
+Then the rule should be created
+And the ruleSource type should be "internal"
+```
+
+#### Scenario: Importing a prebuilt rule without a rule_id field
+
+```Gherkin
+Given the import payload contains a prebuilt rule without a rule_id field
+When the user imports the rule
+Then the import should be rejected with a message "rule_id field is required"
+```
+
+#### Scenario: Importing a prebuilt rule with a matching rule_id but missing a version field
+
+```Gherkin
+Given the import payload contains a prebuilt rule without a version field
+When the user imports the rule
+Then the import should be rejected with a message "version field is required"
+```
+
+#### Scenario: Importing an existing custom rule missing a version field
+
+```Gherkin
+Given the import payload contains an existing custom rule without a version field
+When the user imports the rule
+Then the rule should be updated
+And the ruleSource type should be "internal"
+And the "version" field should be set to the existing rule's "version"
+```
+
+#### Scenario: Importing a new custom rule missing a version field
+
+```Gherkin
+Given the import payload contains a new custom rule without a version field
+When the user imports the rule
+Then the rule should be created
+And the ruleSource type should be "internal"
+And the "version" field should be set to 1
+```
+
+#### Scenario: Importing a rule with overwrite flag set to true
+
+```Gherkin
+Given the import payload contains a rule with an existing rule_id
+And the overwrite flag is set to true
+When the user imports the rule
+Then the rule should be overwritten
+And the ruleSource type should be calculated based on the rule_id and version
+```
+
+#### Scenario: Importing a rule with overwrite flag set to false
+
+```Gherkin
+Given the import payload contains a rule with an existing rule_id
+And the overwrite flag is set to false
+When the user imports the rule
+Then the import should be rejected with a message "rule_id already exists"
+```
+
+#### Scenario: Importing both custom and prebuilt rules
+
+```Gherkin
+Given the import payload contains modified and unmodified, custom and prebuilt rules
+When the user imports the rule
+Then custom rules should be created or updated, with versions defaulted to 1
+And prebuilt rules should be created or updated,
+And prebuilt rules missing versions should be rejected
+```
diff --git a/x-pack/plugins/security_solution/public/app/actions/telemetry.test.ts b/x-pack/plugins/security_solution/public/app/actions/telemetry.test.ts
index 9b22aa03fbeaf..28d4b90f4d9ee 100644
--- a/x-pack/plugins/security_solution/public/app/actions/telemetry.test.ts
+++ b/x-pack/plugins/security_solution/public/app/actions/telemetry.test.ts
@@ -9,6 +9,7 @@ import type { StartServices } from '../../types';
import { enhanceActionWithTelemetry } from './telemetry';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { CellActionExecutionContext } from '@kbn/cell-actions';
+import { AppEventTypes } from '../../common/lib/telemetry';
const actionId = 'test_action_id';
const displayName = 'test-actions';
@@ -29,13 +30,13 @@ const context = {
describe('enhanceActionWithTelemetry', () => {
it('calls telemetry report when the action is executed', () => {
- const telemetry = { reportCellActionClicked: jest.fn() };
+ const telemetry = { reportEvent: jest.fn() };
const services = { telemetry } as unknown as StartServices;
const enhancedAction = enhanceActionWithTelemetry(action, services);
enhancedAction.execute(context);
- expect(telemetry.reportCellActionClicked).toHaveBeenCalledWith({
+ expect(telemetry.reportEvent).toHaveBeenCalledWith(AppEventTypes.CellActionClicked, {
displayName,
actionId,
fieldName,
diff --git a/x-pack/plugins/security_solution/public/app/actions/telemetry.ts b/x-pack/plugins/security_solution/public/app/actions/telemetry.ts
index 319194e57f81a..0c69f7b233666 100644
--- a/x-pack/plugins/security_solution/public/app/actions/telemetry.ts
+++ b/x-pack/plugins/security_solution/public/app/actions/telemetry.ts
@@ -9,6 +9,7 @@ import type { CellAction, CellActionExecutionContext } from '@kbn/cell-actions';
import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import type { StartServices } from '../../types';
import type { SecurityCellActionExecutionContext } from './types';
+import { AppEventTypes } from '../../common/lib/telemetry';
export const enhanceActionWithTelemetry = (
action: CellAction,
@@ -19,7 +20,7 @@ export const enhanceActionWithTelemetry = (
const enhancedExecute = (
context: ActionExecutionContext
): Promise => {
- telemetry.reportCellActionClicked({
+ telemetry.reportEvent(AppEventTypes.CellActionClicked, {
actionId: rest.id,
displayName: rest.getDisplayName(context),
fieldName: context.data.map(({ field }) => field.name).join(', '),
diff --git a/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx b/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx
index d714781ee11c9..b90db333742a4 100644
--- a/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx
@@ -9,6 +9,7 @@ import { renderHook } from '@testing-library/react-hooks';
import { useAssistantTelemetry } from '.';
import { BASE_SECURITY_CONVERSATIONS } from '../content/conversations';
import { createTelemetryServiceMock } from '../../common/lib/telemetry/telemetry_service.mock';
+import { AssistantEventTypes } from '../../common/lib/telemetry';
const customId = `My Convo`;
const mockedConversations = {
@@ -20,15 +21,9 @@ const mockedConversations = {
messages: [],
},
};
-const reportAssistantInvoked = jest.fn();
-const reportAssistantMessageSent = jest.fn();
-const reportAssistantQuickPrompt = jest.fn();
+
const mockedTelemetry = {
...createTelemetryServiceMock(),
- reportAssistantInvoked,
- reportAssistantMessageSent,
- reportAssistantQuickPrompt,
- reportAssistantSettingToggled: () => {},
};
jest.mock('../../common/lib/kibana', () => {
@@ -55,9 +50,9 @@ jest.mock('@kbn/elastic-assistant', () => ({
}));
const trackingFns = [
- 'reportAssistantInvoked',
- 'reportAssistantMessageSent',
- 'reportAssistantQuickPrompt',
+ { name: 'reportAssistantInvoked', eventType: AssistantEventTypes.AssistantInvoked },
+ { name: 'reportAssistantMessageSent', eventType: AssistantEventTypes.AssistantMessageSent },
+ { name: 'reportAssistantQuickPrompt', eventType: AssistantEventTypes.AssistantQuickPrompt },
];
describe('useAssistantTelemetry', () => {
@@ -67,7 +62,7 @@ describe('useAssistantTelemetry', () => {
it('should return the expected telemetry object with tracking functions', () => {
const { result } = renderHook(() => useAssistantTelemetry());
trackingFns.forEach((fn) => {
- expect(result.current).toHaveProperty(fn);
+ expect(result.current).toHaveProperty(fn.name);
});
});
@@ -76,11 +71,11 @@ describe('useAssistantTelemetry', () => {
const { result } = renderHook(() => useAssistantTelemetry());
const validId = Object.keys(mockedConversations)[0];
// @ts-ignore
- const trackingFn = result.current[fn];
+ const trackingFn = result.current[fn.name];
await trackingFn({ conversationId: validId, invokedBy: 'shortcut' });
// @ts-ignore
- const trackingMockedFn = mockedTelemetry[fn];
- expect(trackingMockedFn).toHaveBeenCalledWith({
+ const trackingMockedFn = mockedTelemetry.reportEvent;
+ expect(trackingMockedFn).toHaveBeenCalledWith(fn.eventType, {
conversationId: validId,
invokedBy: 'shortcut',
});
@@ -89,11 +84,11 @@ describe('useAssistantTelemetry', () => {
it('Should call tracking with "Custom" id when tracking is called with an isDefault=false conversation id', async () => {
const { result } = renderHook(() => useAssistantTelemetry());
// @ts-ignore
- const trackingFn = result.current[fn];
+ const trackingFn = result.current[fn.name];
await trackingFn({ conversationId: customId, invokedBy: 'shortcut' });
// @ts-ignore
- const trackingMockedFn = mockedTelemetry[fn];
- expect(trackingMockedFn).toHaveBeenCalledWith({
+ const trackingMockedFn = mockedTelemetry.reportEvent;
+ expect(trackingMockedFn).toHaveBeenCalledWith(fn.eventType, {
conversationId: 'Custom',
invokedBy: 'shortcut',
});
@@ -102,11 +97,11 @@ describe('useAssistantTelemetry', () => {
it('Should call tracking with "Custom" id when tracking is called with an unknown conversation id', async () => {
const { result } = renderHook(() => useAssistantTelemetry());
// @ts-ignore
- const trackingFn = result.current[fn];
+ const trackingFn = result.current[fn.name];
await trackingFn({ conversationId: '123', invokedBy: 'shortcut' });
// @ts-ignore
- const trackingMockedFn = mockedTelemetry[fn];
- expect(trackingMockedFn).toHaveBeenCalledWith({
+ const trackingMockedFn = mockedTelemetry.reportEvent;
+ expect(trackingMockedFn).toHaveBeenCalledWith(fn.eventType, {
conversationId: 'Custom',
invokedBy: 'shortcut',
});
diff --git a/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx b/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx
index 543eac554beba..04bfc8bdcd640 100644
--- a/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx
+++ b/x-pack/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx
@@ -9,7 +9,13 @@ import { type AssistantTelemetry } from '@kbn/elastic-assistant';
import { useCallback } from 'react';
import { useKibana } from '../../common/lib/kibana';
import { useBaseConversations } from '../use_conversation_store';
-
+import type {
+ ReportAssistantInvokedParams,
+ ReportAssistantMessageSentParams,
+ ReportAssistantQuickPromptParams,
+ ReportAssistantSettingToggledParams,
+} from '../../common/lib/telemetry';
+import { AssistantEventTypes } from '../../common/lib/telemetry';
export const useAssistantTelemetry = (): AssistantTelemetry => {
const {
services: { telemetry },
@@ -27,27 +33,30 @@ export const useAssistantTelemetry = (): AssistantTelemetry => {
const reportTelemetry = useCallback(
async ({
- fn,
+ eventType,
params: { conversationId, ...rest },
- }: // eslint-disable-next-line @typescript-eslint/no-explicit-any
- any): Promise<{
- fn: keyof AssistantTelemetry;
- params: AssistantTelemetry[keyof AssistantTelemetry];
- }> =>
- fn({
+ }: {
+ eventType: AssistantEventTypes;
+ params:
+ | ReportAssistantInvokedParams
+ | ReportAssistantMessageSentParams
+ | ReportAssistantQuickPromptParams;
+ }) =>
+ telemetry.reportEvent(eventType, {
...rest,
conversationId: await getAnonymizedConversationTitle(conversationId),
}),
- [getAnonymizedConversationTitle]
+ [getAnonymizedConversationTitle, telemetry]
);
return {
- reportAssistantInvoked: (params) =>
- reportTelemetry({ fn: telemetry.reportAssistantInvoked, params }),
- reportAssistantMessageSent: (params) =>
- reportTelemetry({ fn: telemetry.reportAssistantMessageSent, params }),
- reportAssistantQuickPrompt: (params) =>
- reportTelemetry({ fn: telemetry.reportAssistantQuickPrompt, params }),
- reportAssistantSettingToggled: (params) => telemetry.reportAssistantSettingToggled(params),
+ reportAssistantInvoked: (params: ReportAssistantInvokedParams) =>
+ reportTelemetry({ eventType: AssistantEventTypes.AssistantInvoked, params }),
+ reportAssistantMessageSent: (params: ReportAssistantMessageSentParams) =>
+ reportTelemetry({ eventType: AssistantEventTypes.AssistantMessageSent, params }),
+ reportAssistantQuickPrompt: (params: ReportAssistantQuickPromptParams) =>
+ reportTelemetry({ eventType: AssistantEventTypes.AssistantQuickPrompt, params }),
+ reportAssistantSettingToggled: (params: ReportAssistantSettingToggledParams) =>
+ telemetry.reportEvent(AssistantEventTypes.AssistantSettingToggled, params),
};
};
diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx
index 77fc7db0c0a8a..787dfc973c5d2 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx
@@ -25,6 +25,7 @@ import * as timelineMarkdownPlugin from '../../common/components/markdown_editor
import { useFetchAlertData } from './use_fetch_alert_data';
import { useUpsellingMessage } from '../../common/hooks/use_upselling';
import { useFetchNotes } from '../../notes/hooks/use_fetch_notes';
+import { DocumentEventTypes } from '../../common/lib/telemetry';
const CaseContainerComponent: React.FC = () => {
const { cases, telemetry } = useKibana().services;
@@ -47,7 +48,7 @@ const CaseContainerComponent: React.FC = () => {
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: TimelineId.casePage,
panel: 'right',
});
diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx
index aebf34f094027..a56010182f138 100644
--- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx
@@ -24,6 +24,7 @@ import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/t
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
import { useTourContext } from '../../guided_onboarding_tour';
import { AlertsCasesTourSteps, SecurityStepId } from '../../guided_onboarding_tour/tour_config';
+import { NotesEventTypes, DocumentEventTypes } from '../../../lib/telemetry';
import { getMappedNonEcsValue } from '../../../utils/get_mapped_non_ecs_value';
export type RowActionProps = EuiDataGridCellValueElementProps & {
@@ -109,7 +110,7 @@ const RowActionComponent = ({
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: tableId,
panel: 'right',
});
@@ -137,10 +138,10 @@ const RowActionComponent = ({
},
},
});
- telemetry.reportOpenNoteInExpandableFlyoutClicked({
+ telemetry.reportEvent(NotesEventTypes.OpenNoteInExpandableFlyoutClicked, {
location: tableId,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: tableId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx
index 0648dd60d84f9..83042c2f4fbf4 100644
--- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx
@@ -41,6 +41,7 @@ import {
import type { HostsTableType } from '../../../explore/hosts/store/model';
import type { UsersTableType } from '../../../explore/users/store/model';
import { useGetSecuritySolutionLinkProps, withSecuritySolutionLink } from './link_props';
+import { EntityEventTypes } from '../../lib/telemetry';
export { useSecuritySolutionLinkProps, type GetSecuritySolutionLinkProps } from './link_props';
export { LinkButton, LinkAnchor } from './helpers';
@@ -94,7 +95,7 @@ const UserDetailsLinkComponent: React.FC<{
const onClick = useCallback(
(e: SyntheticEvent) => {
- telemetry.reportEntityDetailsClicked({ entity: 'user' });
+ telemetry.reportEvent(EntityEventTypes.EntityDetailsClicked, { entity: 'user' });
const callback = onClickParam ?? goToUsersDetails;
callback(e);
},
@@ -171,7 +172,7 @@ const HostDetailsLinkComponent: React.FC = ({
const onClick = useCallback(
(e: SyntheticEvent) => {
- telemetry.reportEntityDetailsClicked({ entity: 'host' });
+ telemetry.reportEvent(EntityEventTypes.EntityDetailsClicked, { entity: 'host' });
const callback = onClickParam ?? goToHostDetails;
callback(e);
diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx
index 0801ec37f6ae6..5d1d2ab2eaba7 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx
@@ -11,7 +11,7 @@ import { TestProviders } from '../../../mock';
import type { SecurityJob } from '../types';
import { createTelemetryServiceMock } from '../../../lib/telemetry/telemetry_service.mock';
-import { ML_JOB_TELEMETRY_STATUS } from '../../../lib/telemetry';
+import { ML_JOB_TELEMETRY_STATUS, EntityEventTypes } from '../../../lib/telemetry';
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
@@ -188,14 +188,14 @@ describe('useSecurityJobsHelpers', () => {
await result.current.enableDatafeed(JOB, TIMESTAMP);
});
- expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(EntityEventTypes.MLJobUpdate, {
status: ML_JOB_TELEMETRY_STATUS.moduleInstalled,
isElasticJob: true,
jobId,
moduleId,
});
- expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(EntityEventTypes.MLJobUpdate, {
status: ML_JOB_TELEMETRY_STATUS.started,
isElasticJob: true,
jobId,
@@ -211,7 +211,7 @@ describe('useSecurityJobsHelpers', () => {
await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP);
});
- expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(EntityEventTypes.MLJobUpdate, {
status: ML_JOB_TELEMETRY_STATUS.startError,
errorMessage: 'Start job failure - test_error',
isElasticJob: true,
@@ -228,7 +228,7 @@ describe('useSecurityJobsHelpers', () => {
await result.current.enableDatafeed(JOB, TIMESTAMP);
});
- expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(EntityEventTypes.MLJobUpdate, {
status: ML_JOB_TELEMETRY_STATUS.installationError,
errorMessage: 'Create job failure - test_error',
isElasticJob: true,
@@ -295,7 +295,7 @@ describe('useSecurityJobsHelpers', () => {
await result.current.disableDatafeed({ ...JOB, isInstalled: true });
});
- expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(EntityEventTypes.MLJobUpdate, {
status: ML_JOB_TELEMETRY_STATUS.stopped,
isElasticJob: true,
jobId,
@@ -311,7 +311,7 @@ describe('useSecurityJobsHelpers', () => {
await result.current.disableDatafeed({ ...JOB, isInstalled: true });
});
- expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(EntityEventTypes.MLJobUpdate, {
status: ML_JOB_TELEMETRY_STATUS.stopError,
errorMessage: 'Stop job failure - test_error',
isElasticJob: true,
diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts
index 393e132436c38..ab966770aaf33 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts
+++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts
@@ -13,6 +13,7 @@ import {
METRIC_TYPE,
ML_JOB_TELEMETRY_STATUS,
TELEMETRY_EVENT,
+ EntityEventTypes,
track,
} from '../../../lib/telemetry';
@@ -43,7 +44,7 @@ export const useEnableDataFeed = () => {
jobIdErrorFilter: [job.id],
groups: job.groups,
});
- telemetry.reportMLJobUpdate({
+ telemetry.reportEvent(EntityEventTypes.MLJobUpdate, {
jobId: job.id,
isElasticJob: job.isElasticJob,
moduleId: job.moduleId,
@@ -52,7 +53,7 @@ export const useEnableDataFeed = () => {
} catch (error) {
setIsLoading(false);
addError(error, { title: i18n.CREATE_JOB_FAILURE });
- telemetry.reportMLJobUpdate({
+ telemetry.reportEvent(EntityEventTypes.MLJobUpdate, {
jobId: job.id,
isElasticJob: job.isElasticJob,
moduleId: job.moduleId,
@@ -82,7 +83,7 @@ export const useEnableDataFeed = () => {
throw new Error(response[datafeedId].error);
}
- telemetry.reportMLJobUpdate({
+ telemetry.reportEvent(EntityEventTypes.MLJobUpdate, {
jobId: job.id,
isElasticJob: job.isElasticJob,
status: ML_JOB_TELEMETRY_STATUS.started,
@@ -92,7 +93,7 @@ export const useEnableDataFeed = () => {
} catch (error) {
track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE);
addError(error, { title: i18n.START_JOB_FAILURE });
- telemetry.reportMLJobUpdate({
+ telemetry.reportEvent(EntityEventTypes.MLJobUpdate, {
jobId: job.id,
isElasticJob: job.isElasticJob,
status: ML_JOB_TELEMETRY_STATUS.startError,
@@ -124,7 +125,7 @@ export const useEnableDataFeed = () => {
throw new Error(response.error);
}
- telemetry.reportMLJobUpdate({
+ telemetry.reportEvent(EntityEventTypes.MLJobUpdate, {
jobId: job.id,
isElasticJob: job.isElasticJob,
status: ML_JOB_TELEMETRY_STATUS.stopped,
@@ -134,7 +135,7 @@ export const useEnableDataFeed = () => {
} catch (error) {
track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE);
addError(error, { title: i18n.STOP_JOB_FAILURE });
- telemetry.reportMLJobUpdate({
+ telemetry.reportEvent(EntityEventTypes.MLJobUpdate, {
jobId: job.id,
isElasticJob: job.isElasticJob,
status: ML_JOB_TELEMETRY_STATUS.stopError,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.ts
index c8c675f0f40a5..c20d1f4623fa7 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.test.ts
@@ -137,12 +137,12 @@ describe('useBreadcrumbsNav', () => {
});
it('should create breadcrumbs onClick handler', () => {
- const reportBreadcrumbClickedMock = jest.fn();
+ const reportEventMock = jest.fn();
(kibanaLib.useKibana as jest.Mock).mockImplementation(() => ({
services: {
telemetry: {
- reportBreadcrumbClicked: reportBreadcrumbClickedMock,
+ reportEvent: reportEventMock,
},
},
}));
@@ -157,6 +157,6 @@ describe('useBreadcrumbsNav', () => {
expect(event.preventDefault).toHaveBeenCalled();
expect(mockDispatch).toHaveBeenCalled();
- expect(reportBreadcrumbClickedMock).toHaveBeenCalled();
+ expect(reportEventMock).toHaveBeenCalled();
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts
index 7825435fafcad..aae96ddd07dc2 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/use_breadcrumbs_nav.ts
@@ -16,7 +16,7 @@ import { timelineActions } from '../../../../timelines/store';
import { TimelineId } from '../../../../../common/types/timeline';
import type { GetSecuritySolutionUrl } from '../../link_to';
import { useGetSecuritySolutionUrl } from '../../link_to';
-import type { TelemetryClientStart } from '../../../lib/telemetry';
+import { AppEventTypes, type TelemetryServiceStart } from '../../../lib/telemetry';
import { useKibana, useNavigateTo, type NavigateTo } from '../../../lib/kibana';
import { useRouteSpy } from '../../../utils/route/use_route_spy';
import { updateBreadcrumbsNav } from '../../../breadcrumbs';
@@ -68,7 +68,7 @@ const addOnClicksHandlers = (
breadcrumbs: ChromeBreadcrumb[],
dispatch: Dispatch,
navigateTo: NavigateTo,
- telemetry: TelemetryClientStart
+ telemetry: TelemetryServiceStart
): ChromeBreadcrumb[] =>
breadcrumbs.map((breadcrumb) => ({
...breadcrumb,
@@ -89,13 +89,13 @@ const createOnClickHandler =
href: string,
dispatch: Dispatch,
navigateTo: NavigateTo,
- telemetry: TelemetryClientStart,
+ telemetry: TelemetryServiceStart,
title: React.ReactNode
) =>
(ev: SyntheticEvent) => {
ev.preventDefault();
if (typeof title === 'string') {
- telemetry.reportBreadcrumbClicked({ title });
+ telemetry.reportEvent(AppEventTypes.BreadcrumbClicked, { title });
}
dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false }));
navigateTo({ url: href });
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts
index 5d0e9bcfd918a..08bc1d4a62a83 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts
@@ -52,52 +52,3 @@ export enum TELEMETRY_EVENT {
// AI assistant on rule creation form
OPEN_ASSISTANT_ON_RULE_QUERY_ERROR = 'open_assistant_on_rule_query_error',
}
-
-export enum TelemetryEventTypes {
- AlertsGroupingChanged = 'Alerts Grouping Changed',
- AlertsGroupingToggled = 'Alerts Grouping Toggled',
- AlertsGroupingTakeAction = 'Alerts Grouping Take Action',
- BreadcrumbClicked = 'Breadcrumb Clicked',
- AssistantInvoked = 'Assistant Invoked',
- AssistantMessageSent = 'Assistant Message Sent',
- AssistantQuickPrompt = 'Assistant Quick Prompt',
- AssistantSettingToggled = 'Assistant Setting Toggled',
- AssetCriticalityCsvPreviewGenerated = 'Asset Criticality Csv Preview Generated',
- AssetCriticalityFileSelected = 'Asset Criticality File Selected',
- AssetCriticalityCsvImported = 'Asset Criticality CSV Imported',
- EntityDetailsClicked = 'Entity Details Clicked',
- EntityAlertsClicked = 'Entity Alerts Clicked',
- EntityRiskFiltered = 'Entity Risk Filtered',
- EntityStoreEnablementToggleClicked = 'Entity Store Enablement Toggle Clicked',
- EntityStoreDashboardInitButtonClicked = 'Entity Store Initialization Button Clicked',
- MLJobUpdate = 'ML Job Update',
- AddRiskInputToTimelineClicked = 'Add Risk Input To Timeline Clicked',
- ToggleRiskSummaryClicked = 'Toggle Risk Summary Clicked',
- RiskInputsExpandedFlyoutOpened = 'Risk Inputs Expanded Flyout Opened',
- CellActionClicked = 'Cell Action Clicked',
- AnomaliesCountClicked = 'Anomalies Count Clicked',
- DataQualityIndexChecked = 'Data Quality Index Checked',
- DataQualityCheckAllCompleted = 'Data Quality Check All Completed',
- DetailsFlyoutOpened = 'Details Flyout Opened',
- DetailsFlyoutTabClicked = 'Details Flyout Tabs Clicked',
- OnboardingHubStepOpen = 'Onboarding Hub Step Open',
- OnboardingHubStepFinished = 'Onboarding Hub Step Finished',
- OnboardingHubStepLinkClicked = 'Onboarding Hub Step Link Clicked',
- ManualRuleRunOpenModal = 'Manual Rule Run Open Modal',
- ManualRuleRunExecute = 'Manual Rule Run Execute',
- ManualRuleRunCancelJob = 'Manual Rule Run Cancel Job',
- EventLogFilterByRunType = 'Event Log Filter By Run Type',
- EventLogShowSourceEventDateRange = 'Event Log -> Show Source -> Event Date Range',
- OpenNoteInExpandableFlyoutClicked = 'Open Note In Expandable Flyout Clicked',
- AddNoteFromExpandableFlyoutClicked = 'Add Note From Expandable Flyout Clicked',
- PreviewRule = 'Preview rule',
-}
-
-export enum ML_JOB_TELEMETRY_STATUS {
- started = 'started',
- startError = 'start_error',
- stopped = 'stopped',
- stopError = 'stop_error',
- moduleInstalled = 'module_installed',
- installationError = 'installationError',
-}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/index.ts
index 117d6216ed2ab..70d2eb82a2c91 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { AssistantTelemetryEvent } from './types';
+import { AssistantEventTypes } from './types';
-export const assistantInvokedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssistantInvoked,
+export const assistantInvokedEvent: AssistantTelemetryEvent = {
+ eventType: AssistantEventTypes.AssistantInvoked,
schema: {
conversationId: {
type: 'keyword',
@@ -28,8 +28,8 @@ export const assistantInvokedEvent: TelemetryEvent = {
},
};
-export const assistantMessageSentEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssistantMessageSent,
+export const assistantMessageSentEvent: AssistantTelemetryEvent = {
+ eventType: AssistantEventTypes.AssistantMessageSent,
schema: {
conversationId: {
type: 'keyword',
@@ -75,8 +75,8 @@ export const assistantMessageSentEvent: TelemetryEvent = {
},
};
-export const assistantQuickPrompt: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssistantQuickPrompt,
+export const assistantQuickPrompt: AssistantTelemetryEvent = {
+ eventType: AssistantEventTypes.AssistantQuickPrompt,
schema: {
conversationId: {
type: 'keyword',
@@ -95,8 +95,8 @@ export const assistantQuickPrompt: TelemetryEvent = {
},
};
-export const assistantSettingToggledEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssistantSettingToggled,
+export const assistantSettingToggledEvent: AssistantTelemetryEvent = {
+ eventType: AssistantEventTypes.AssistantSettingToggled,
schema: {
alertsCountUpdated: {
type: 'boolean',
@@ -114,3 +114,10 @@ export const assistantSettingToggledEvent: TelemetryEvent = {
},
},
};
+
+export const assistantTelemetryEvents = [
+ assistantInvokedEvent,
+ assistantMessageSentEvent,
+ assistantQuickPrompt,
+ assistantSettingToggledEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts
index 2dd6bf6215dbf..894494575f9af 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts
@@ -6,7 +6,13 @@
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
+
+export enum AssistantEventTypes {
+ AssistantInvoked = 'Assistant Invoked',
+ AssistantMessageSent = 'Assistant Message Sent',
+ AssistantQuickPrompt = 'Assistant Quick Prompt',
+ AssistantSettingToggled = 'Assistant Setting Toggled',
+}
export interface ReportAssistantInvokedParams {
conversationId: string;
@@ -32,26 +38,14 @@ export interface ReportAssistantSettingToggledParams {
assistantStreamingEnabled?: boolean;
}
-export type ReportAssistantTelemetryEventParams =
- | ReportAssistantInvokedParams
- | ReportAssistantMessageSentParams
- | ReportAssistantSettingToggledParams
- | ReportAssistantQuickPromptParams;
-
-export type AssistantTelemetryEvent =
- | {
- eventType: TelemetryEventTypes.AssistantInvoked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AssistantSettingToggled;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AssistantMessageSent;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AssistantQuickPrompt;
- schema: RootSchema;
- };
+export interface AssistantTelemetryEventsMap {
+ [AssistantEventTypes.AssistantInvoked]: ReportAssistantInvokedParams;
+ [AssistantEventTypes.AssistantMessageSent]: ReportAssistantMessageSentParams;
+ [AssistantEventTypes.AssistantQuickPrompt]: ReportAssistantQuickPromptParams;
+ [AssistantEventTypes.AssistantSettingToggled]: ReportAssistantSettingToggledParams;
+}
+
+export interface AssistantTelemetryEvent {
+ eventType: AssistantEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/index.ts
index 7c990dc75776e..3e2119205b77c 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { AlertsGroupingTelemetryEvent } from './types';
+import { AlertsEventTypes } from './types';
-export const alertsGroupingToggledEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AlertsGroupingToggled,
+export const alertsGroupingToggledEvent: AlertsGroupingTelemetryEvent = {
+ eventType: AlertsEventTypes.AlertsGroupingToggled,
schema: {
isOpen: {
type: 'boolean',
@@ -35,8 +35,8 @@ export const alertsGroupingToggledEvent: TelemetryEvent = {
},
};
-export const alertsGroupingChangedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AlertsGroupingChanged,
+export const alertsGroupingChangedEvent: AlertsGroupingTelemetryEvent = {
+ eventType: AlertsEventTypes.AlertsGroupingChanged,
schema: {
tableId: {
type: 'keyword',
@@ -55,8 +55,8 @@ export const alertsGroupingChangedEvent: TelemetryEvent = {
},
};
-export const alertsGroupingTakeActionEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AlertsGroupingTakeAction,
+export const alertsGroupingTakeActionEvent: AlertsGroupingTelemetryEvent = {
+ eventType: AlertsEventTypes.AlertsGroupingTakeAction,
schema: {
tableId: {
type: 'keyword',
@@ -88,3 +88,9 @@ export const alertsGroupingTakeActionEvent: TelemetryEvent = {
},
},
};
+
+export const alertsTelemetryEvents = [
+ alertsGroupingToggledEvent,
+ alertsGroupingChangedEvent,
+ alertsGroupingTakeActionEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts
index d2b5e227ee66a..924ddd4d1987f 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts
@@ -6,41 +6,38 @@
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export interface ReportAlertsGroupingChangedParams {
+export enum AlertsEventTypes {
+ AlertsGroupingChanged = 'Alerts Grouping Changed',
+ AlertsGroupingToggled = 'Alerts Grouping Toggled',
+ AlertsGroupingTakeAction = 'Alerts Grouping Take Action',
+}
+
+interface ReportAlertsGroupingChangedParams {
tableId: string;
groupByField: string;
}
-export interface ReportAlertsGroupingToggledParams {
+interface ReportAlertsGroupingToggledParams {
isOpen: boolean;
tableId: string;
groupNumber: number;
}
-export interface ReportAlertsTakeActionParams {
+interface ReportAlertsTakeActionParams {
tableId: string;
groupNumber: number;
status: 'open' | 'closed' | 'acknowledged';
groupByField: string;
}
-export type ReportAlertsGroupingTelemetryEventParams =
- | ReportAlertsGroupingChangedParams
- | ReportAlertsGroupingToggledParams
- | ReportAlertsTakeActionParams;
+export interface AlertsGroupingTelemetryEventsMap {
+ [AlertsEventTypes.AlertsGroupingChanged]: ReportAlertsGroupingChangedParams;
+ [AlertsEventTypes.AlertsGroupingToggled]: ReportAlertsGroupingToggledParams;
+ [AlertsEventTypes.AlertsGroupingTakeAction]: ReportAlertsTakeActionParams;
+}
-export type AlertsGroupingTelemetryEvent =
- | {
- eventType: TelemetryEventTypes.AlertsGroupingToggled;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AlertsGroupingChanged;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AlertsGroupingTakeAction;
- schema: RootSchema;
- };
+export interface AlertsGroupingTelemetryEvent {
+ eventType: AlertsEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/app/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/app/index.ts
new file mode 100644
index 0000000000000..d00d1df5f8306
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/app/index.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 type { AppTelemetryEvent } from './types';
+import { AppEventTypes } from './types';
+
+const cellActionClickedEvent: AppTelemetryEvent = {
+ eventType: AppEventTypes.CellActionClicked,
+ schema: {
+ fieldName: {
+ type: 'keyword',
+ _meta: {
+ description: 'Field Name',
+ optional: false,
+ },
+ },
+ actionId: {
+ type: 'keyword',
+ _meta: {
+ description: 'Action id',
+ optional: false,
+ },
+ },
+ displayName: {
+ type: 'keyword',
+ _meta: {
+ description: 'User friendly action name',
+ optional: false,
+ },
+ },
+ metadata: {
+ type: 'pass_through',
+ _meta: {
+ description: 'Action metadata',
+ optional: true,
+ },
+ },
+ },
+};
+
+const breadCrumbClickedEvent: AppTelemetryEvent = {
+ eventType: AppEventTypes.BreadcrumbClicked,
+ schema: {
+ title: {
+ type: 'keyword',
+ _meta: {
+ description: 'Breadcrumb title',
+ optional: false,
+ },
+ },
+ },
+};
+
+export const appTelemetryEvents = [cellActionClickedEvent, breadCrumbClickedEvent];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/app/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/app/types.ts
new file mode 100644
index 0000000000000..f42e689cc3fdb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/app/types.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import type { RootSchema } from '@kbn/core/public';
+import type { SecurityCellActionMetadata } from '../../../../../app/actions/types';
+
+export enum AppEventTypes {
+ CellActionClicked = 'Cell Action Clicked',
+ BreadcrumbClicked = 'Breadcrumb Clicked',
+}
+
+interface ReportCellActionClickedParams {
+ metadata: SecurityCellActionMetadata | undefined;
+ displayName: string;
+ actionId: string;
+ fieldName: string;
+}
+
+interface ReportBreadcrumbClickedParams {
+ title: string;
+}
+
+export interface AppTelemetryEventsMap {
+ [AppEventTypes.CellActionClicked]: ReportCellActionClickedParams;
+ [AppEventTypes.BreadcrumbClicked]: ReportBreadcrumbClickedParams;
+}
+
+export interface AppTelemetryEvent {
+ eventType: AppEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts
index 1a3a88cbd2f57..16e8a3e1eaa64 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts
@@ -5,14 +5,10 @@
* 2.0.
*/
-import { TelemetryEventTypes } from '../../constants';
-import type {
- DataQualityTelemetryCheckAllCompletedEvent,
- DataQualityTelemetryIndexCheckedEvent,
-} from '../../types';
+import { DataQualityEventTypes, type DataQualityTelemetryEvents } from './types';
-export const dataQualityIndexCheckedEvent: DataQualityTelemetryIndexCheckedEvent = {
- eventType: TelemetryEventTypes.DataQualityIndexChecked,
+export const dataQualityIndexCheckedEvent: DataQualityTelemetryEvents = {
+ eventType: DataQualityEventTypes.DataQualityIndexChecked,
schema: {
batchId: {
type: 'keyword',
@@ -163,8 +159,8 @@ export const dataQualityIndexCheckedEvent: DataQualityTelemetryIndexCheckedEvent
},
};
-export const dataQualityCheckAllClickedEvent: DataQualityTelemetryCheckAllCompletedEvent = {
- eventType: TelemetryEventTypes.DataQualityCheckAllCompleted,
+export const dataQualityCheckAllClickedEvent: DataQualityTelemetryEvents = {
+ eventType: DataQualityEventTypes.DataQualityCheckAllCompleted,
schema: {
batchId: {
type: 'keyword',
@@ -259,3 +255,8 @@ export const dataQualityCheckAllClickedEvent: DataQualityTelemetryCheckAllComple
},
},
};
+
+export const dataQualityTelemetryEvents = [
+ dataQualityIndexCheckedEvent,
+ dataQualityCheckAllClickedEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts
index 9e1d012811e3b..a6eca7eafc9c5 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts
@@ -6,7 +6,11 @@
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
+
+export enum DataQualityEventTypes {
+ DataQualityIndexChecked = 'Data Quality Index Checked',
+ DataQualityCheckAllCompleted = 'Data Quality Check All Completed',
+}
export type ReportDataQualityIndexCheckedParams = ReportDataQualityCheckAllCompletedParams & {
errorCount?: number;
@@ -34,16 +38,12 @@ export interface ReportDataQualityCheckAllCompletedParams {
timeConsumedMs?: number;
}
-export interface DataQualityTelemetryIndexCheckedEvent {
- eventType: TelemetryEventTypes.DataQualityIndexChecked;
- schema: RootSchema;
+export interface DataQualityTelemetryEventsMap {
+ [DataQualityEventTypes.DataQualityIndexChecked]: ReportDataQualityIndexCheckedParams;
+ [DataQualityEventTypes.DataQualityCheckAllCompleted]: ReportDataQualityCheckAllCompletedParams;
}
-export interface DataQualityTelemetryCheckAllCompletedEvent {
- eventType: TelemetryEventTypes.DataQualityCheckAllCompleted;
- schema: RootSchema;
+export interface DataQualityTelemetryEvents {
+ eventType: DataQualityEventTypes;
+ schema: RootSchema;
}
-
-export type DataQualityTelemetryEvents =
- | DataQualityTelemetryIndexCheckedEvent
- | DataQualityTelemetryCheckAllCompletedEvent;
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts
index ba59cf5130dc2..6cb27693464b2 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { DocumentDetailsTelemetryEvent } from './types';
+import { DocumentEventTypes } from './types';
-export const DocumentDetailsFlyoutOpenedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.DetailsFlyoutOpened,
+export const DocumentDetailsFlyoutOpenedEvent: DocumentDetailsTelemetryEvent = {
+ eventType: DocumentEventTypes.DetailsFlyoutOpened,
schema: {
location: {
type: 'text',
@@ -28,8 +28,8 @@ export const DocumentDetailsFlyoutOpenedEvent: TelemetryEvent = {
},
};
-export const DocumentDetailsTabClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.DetailsFlyoutTabClicked,
+export const DocumentDetailsTabClickedEvent: DocumentDetailsTelemetryEvent = {
+ eventType: DocumentEventTypes.DetailsFlyoutTabClicked,
schema: {
location: {
type: 'text',
@@ -54,3 +54,8 @@ export const DocumentDetailsTabClickedEvent: TelemetryEvent = {
},
},
};
+
+export const documentTelemetryEvents = [
+ DocumentDetailsFlyoutOpenedEvent,
+ DocumentDetailsTabClickedEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts
index 7a3ff374eae3c..603b169e77403 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts
@@ -6,29 +6,29 @@
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export interface ReportDetailsFlyoutOpenedParams {
+export enum DocumentEventTypes {
+ DetailsFlyoutOpened = 'Details Flyout Opened',
+ DetailsFlyoutTabClicked = 'Details Flyout Tabs Clicked',
+}
+
+interface ReportDetailsFlyoutOpenedParams {
location: string;
panel: 'left' | 'right' | 'preview';
}
-export interface ReportDetailsFlyoutTabClickedParams {
+interface ReportDetailsFlyoutTabClickedParams {
location: string;
panel: 'left' | 'right';
tabId: string;
}
-export type ReportDocumentDetailsTelemetryEventParams =
- | ReportDetailsFlyoutOpenedParams
- | ReportDetailsFlyoutTabClickedParams;
+export interface DocumentDetailsTelemetryEventsMap {
+ [DocumentEventTypes.DetailsFlyoutOpened]: ReportDetailsFlyoutOpenedParams;
+ [DocumentEventTypes.DetailsFlyoutTabClicked]: ReportDetailsFlyoutTabClickedParams;
+}
-export type DocumentDetailsTelemetryEvents =
- | {
- eventType: TelemetryEventTypes.DetailsFlyoutOpened;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.DetailsFlyoutTabClicked;
- schema: RootSchema;
- };
+export interface DocumentDetailsTelemetryEvent {
+ eventType: DocumentEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/index.ts
index 5a45970de6af1..771957d7a8829 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { EntityAnalyticsTelemetryEvent } from './types';
+import { EntityEventTypes } from './types';
-export const entityClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.EntityDetailsClicked,
+export const entityClickedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.EntityDetailsClicked,
schema: {
entity: {
type: 'keyword',
@@ -21,8 +21,8 @@ export const entityClickedEvent: TelemetryEvent = {
},
};
-export const entityAlertsClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.EntityAlertsClicked,
+export const entityAlertsClickedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.EntityAlertsClicked,
schema: {
entity: {
type: 'keyword',
@@ -34,8 +34,8 @@ export const entityAlertsClickedEvent: TelemetryEvent = {
},
};
-export const entityRiskFilteredEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.EntityRiskFiltered,
+export const entityRiskFilteredEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.EntityRiskFiltered,
schema: {
entity: {
type: 'keyword',
@@ -54,8 +54,8 @@ export const entityRiskFilteredEvent: TelemetryEvent = {
},
};
-export const toggleRiskSummaryClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.ToggleRiskSummaryClicked,
+export const toggleRiskSummaryClickedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.ToggleRiskSummaryClicked,
schema: {
entity: {
type: 'keyword',
@@ -74,8 +74,8 @@ export const toggleRiskSummaryClickedEvent: TelemetryEvent = {
},
};
-export const RiskInputsExpandedFlyoutOpenedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.RiskInputsExpandedFlyoutOpened,
+export const RiskInputsExpandedFlyoutOpenedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.RiskInputsExpandedFlyoutOpened,
schema: {
entity: {
type: 'keyword',
@@ -87,8 +87,8 @@ export const RiskInputsExpandedFlyoutOpenedEvent: TelemetryEvent = {
},
};
-export const addRiskInputToTimelineClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AddRiskInputToTimelineClicked,
+export const addRiskInputToTimelineClickedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.AddRiskInputToTimelineClicked,
schema: {
quantity: {
type: 'integer',
@@ -100,8 +100,8 @@ export const addRiskInputToTimelineClickedEvent: TelemetryEvent = {
},
};
-export const assetCriticalityFileSelectedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssetCriticalityFileSelected,
+export const assetCriticalityFileSelectedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.AssetCriticalityFileSelected,
schema: {
valid: {
type: 'boolean',
@@ -131,8 +131,8 @@ export const assetCriticalityFileSelectedEvent: TelemetryEvent = {
},
};
-export const assetCriticalityCsvPreviewGeneratedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssetCriticalityCsvPreviewGenerated,
+export const assetCriticalityCsvPreviewGeneratedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.AssetCriticalityCsvPreviewGenerated,
schema: {
file: {
properties: {
@@ -198,8 +198,8 @@ export const assetCriticalityCsvPreviewGeneratedEvent: TelemetryEvent = {
},
};
-export const assetCriticalityCsvImportedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AssetCriticalityCsvImported,
+export const assetCriticalityCsvImportedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.AssetCriticalityCsvImported,
schema: {
file: {
properties: {
@@ -215,8 +215,8 @@ export const assetCriticalityCsvImportedEvent: TelemetryEvent = {
},
};
-export const entityStoreInitEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.EntityStoreDashboardInitButtonClicked,
+export const entityStoreInitEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.EntityStoreDashboardInitButtonClicked,
schema: {
timestamp: {
type: 'date',
@@ -228,8 +228,8 @@ export const entityStoreInitEvent: TelemetryEvent = {
},
};
-export const entityStoreEnablementEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.EntityStoreEnablementToggleClicked,
+export const entityStoreEnablementEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.EntityStoreEnablementToggleClicked,
schema: {
timestamp: {
type: 'date',
@@ -247,3 +247,80 @@ export const entityStoreEnablementEvent: TelemetryEvent = {
},
},
};
+
+const mlJobUpdateEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.MLJobUpdate,
+ schema: {
+ jobId: {
+ type: 'keyword',
+ _meta: {
+ description: 'Job id',
+ optional: false,
+ },
+ },
+ isElasticJob: {
+ type: 'boolean',
+ _meta: {
+ description: 'If true the job is one of the pre-configure security solution modules',
+ optional: false,
+ },
+ },
+ moduleId: {
+ type: 'keyword',
+ _meta: {
+ description: 'Module id',
+ optional: true,
+ },
+ },
+ status: {
+ type: 'keyword',
+ _meta: {
+ description: 'It describes what has changed in the job.',
+ optional: false,
+ },
+ },
+ errorMessage: {
+ type: 'text',
+ _meta: {
+ description: 'Error message',
+ optional: true,
+ },
+ },
+ },
+};
+
+const anomaliesCountClickedEvent: EntityAnalyticsTelemetryEvent = {
+ eventType: EntityEventTypes.AnomaliesCountClicked,
+ schema: {
+ jobId: {
+ type: 'keyword',
+ _meta: {
+ description: 'Job id',
+ optional: false,
+ },
+ },
+ count: {
+ type: 'integer',
+ _meta: {
+ description: 'Number of anomalies',
+ optional: false,
+ },
+ },
+ },
+};
+
+export const entityTelemetryEvents = [
+ entityClickedEvent,
+ entityAlertsClickedEvent,
+ entityRiskFilteredEvent,
+ assetCriticalityCsvPreviewGeneratedEvent,
+ assetCriticalityFileSelectedEvent,
+ assetCriticalityCsvImportedEvent,
+ entityStoreEnablementEvent,
+ entityStoreInitEvent,
+ toggleRiskSummaryClickedEvent,
+ RiskInputsExpandedFlyoutOpenedEvent,
+ addRiskInputToTimelineClickedEvent,
+ mlJobUpdateEvent,
+ anomaliesCountClickedEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts
index 3313e99a31184..3051d675b6b19 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts
@@ -7,29 +7,52 @@
import type { RootSchema } from '@kbn/core/public';
import type { RiskSeverity } from '../../../../../../common/search_strategy';
-import type { TelemetryEventTypes } from '../../constants';
+export enum EntityEventTypes {
+ EntityDetailsClicked = 'Entity Details Clicked',
+ EntityAlertsClicked = 'Entity Alerts Clicked',
+ EntityRiskFiltered = 'Entity Risk Filtered',
+ EntityStoreEnablementToggleClicked = 'Entity Store Enablement Toggle Clicked',
+ EntityStoreDashboardInitButtonClicked = 'Entity Store Initialization Button Clicked',
+ ToggleRiskSummaryClicked = 'Toggle Risk Summary Clicked',
+ AddRiskInputToTimelineClicked = 'Add Risk Input To Timeline Clicked',
+ RiskInputsExpandedFlyoutOpened = 'Risk Inputs Expanded Flyout Opened',
+ AssetCriticalityCsvPreviewGenerated = 'Asset Criticality Csv Preview Generated',
+ AssetCriticalityFileSelected = 'Asset Criticality File Selected',
+ AssetCriticalityCsvImported = 'Asset Criticality CSV Imported',
+ AnomaliesCountClicked = 'Anomalies Count Clicked',
+ MLJobUpdate = 'ML Job Update',
+}
+
+export enum ML_JOB_TELEMETRY_STATUS {
+ started = 'started',
+ startError = 'start_error',
+ stopped = 'stopped',
+ stopError = 'stop_error',
+ moduleInstalled = 'module_installed',
+ installationError = 'installationError',
+}
interface EntityParam {
entity: 'host' | 'user';
}
-export type ReportEntityDetailsClickedParams = EntityParam;
-export type ReportEntityAlertsClickedParams = EntityParam;
-export interface ReportEntityRiskFilteredParams extends Partial {
+type ReportEntityDetailsClickedParams = EntityParam;
+type ReportEntityAlertsClickedParams = EntityParam;
+interface ReportEntityRiskFilteredParams extends Partial {
selectedSeverity: RiskSeverity;
}
-export interface ReportToggleRiskSummaryClickedParams extends EntityParam {
+interface ReportToggleRiskSummaryClickedParams extends EntityParam {
action: 'show' | 'hide';
}
-export type ReportRiskInputsExpandedFlyoutOpenedParams = EntityParam;
+type ReportRiskInputsExpandedFlyoutOpenedParams = EntityParam;
-export interface ReportAddRiskInputToTimelineClickedParams {
+interface ReportAddRiskInputToTimelineClickedParams {
quantity: number;
}
-export interface ReportAssetCriticalityFileSelectedParams {
+interface ReportAssetCriticalityFileSelectedParams {
valid: boolean;
errorCode?: string;
file: {
@@ -37,7 +60,7 @@ export interface ReportAssetCriticalityFileSelectedParams {
};
}
-export interface ReportAssetCriticalityCsvPreviewGeneratedParams {
+interface ReportAssetCriticalityCsvPreviewGeneratedParams {
file: {
size: number;
};
@@ -53,76 +76,51 @@ export interface ReportAssetCriticalityCsvPreviewGeneratedParams {
};
}
-export interface ReportAssetCriticalityCsvImportedParams {
+interface ReportAssetCriticalityCsvImportedParams {
file: {
size: number;
};
}
-export interface ReportEntityStoreEnablementParams {
+interface ReportAnomaliesCountClickedParams {
+ jobId: string;
+ count: number;
+}
+
+interface ReportEntityStoreEnablementParams {
timestamp: string;
action: 'start' | 'stop';
}
-export interface ReportEntityStoreInitParams {
+interface ReportEntityStoreInitParams {
timestamp: string;
}
-export type ReportEntityAnalyticsTelemetryEventParams =
- | ReportEntityDetailsClickedParams
- | ReportEntityAlertsClickedParams
- | ReportEntityRiskFilteredParams
- | ReportToggleRiskSummaryClickedParams
- | ReportRiskInputsExpandedFlyoutOpenedParams
- | ReportAddRiskInputToTimelineClickedParams
- | ReportAssetCriticalityCsvPreviewGeneratedParams
- | ReportAssetCriticalityFileSelectedParams
- | ReportAssetCriticalityCsvImportedParams
- | ReportEntityStoreEnablementParams
- | ReportEntityStoreInitParams;
-
-export type EntityAnalyticsTelemetryEvent =
- | {
- eventType: TelemetryEventTypes.EntityDetailsClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.EntityAlertsClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.EntityRiskFiltered;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AddRiskInputToTimelineClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.ToggleRiskSummaryClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.RiskInputsExpandedFlyoutOpened;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AssetCriticalityCsvPreviewGenerated;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AssetCriticalityFileSelected;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AssetCriticalityCsvImported;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.EntityStoreEnablementToggleClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.EntityStoreDashboardInitButtonClicked;
- schema: RootSchema;
- };
+interface ReportMLJobUpdateParams {
+ jobId: string;
+ isElasticJob: boolean;
+ status: ML_JOB_TELEMETRY_STATUS;
+ moduleId?: string;
+ errorMessage?: string;
+}
+
+export interface EntityAnalyticsTelemetryEventsMap {
+ [EntityEventTypes.EntityDetailsClicked]: ReportEntityDetailsClickedParams;
+ [EntityEventTypes.EntityAlertsClicked]: ReportEntityAlertsClickedParams;
+ [EntityEventTypes.EntityRiskFiltered]: ReportEntityRiskFilteredParams;
+ [EntityEventTypes.EntityStoreEnablementToggleClicked]: ReportEntityStoreEnablementParams;
+ [EntityEventTypes.EntityStoreDashboardInitButtonClicked]: ReportEntityStoreInitParams;
+ [EntityEventTypes.ToggleRiskSummaryClicked]: ReportToggleRiskSummaryClickedParams;
+ [EntityEventTypes.AddRiskInputToTimelineClicked]: ReportAddRiskInputToTimelineClickedParams;
+ [EntityEventTypes.RiskInputsExpandedFlyoutOpened]: ReportRiskInputsExpandedFlyoutOpenedParams;
+ [EntityEventTypes.AssetCriticalityCsvPreviewGenerated]: ReportAssetCriticalityCsvPreviewGeneratedParams;
+ [EntityEventTypes.AssetCriticalityFileSelected]: ReportAssetCriticalityFileSelectedParams;
+ [EntityEventTypes.AssetCriticalityCsvImported]: ReportAssetCriticalityCsvImportedParams;
+ [EntityEventTypes.AnomaliesCountClicked]: ReportAnomaliesCountClickedParams;
+ [EntityEventTypes.MLJobUpdate]: ReportMLJobUpdateParams;
+}
+
+export interface EntityAnalyticsTelemetryEvent {
+ eventType: EntityEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts
index c30efcee10cfc..7e34afa0aec66 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts
@@ -6,10 +6,10 @@
*/
import type { EventLogTelemetryEvent } from './types';
-import { TelemetryEventTypes } from '../../constants';
+import { EventLogEventTypes } from './types';
export const eventLogFilterByRunTypeEvent: EventLogTelemetryEvent = {
- eventType: TelemetryEventTypes.EventLogFilterByRunType,
+ eventType: EventLogEventTypes.EventLogFilterByRunType,
schema: {
runType: {
type: 'array',
@@ -24,7 +24,7 @@ export const eventLogFilterByRunTypeEvent: EventLogTelemetryEvent = {
};
export const eventLogShowSourceEventDateRangeEvent: EventLogTelemetryEvent = {
- eventType: TelemetryEventTypes.EventLogShowSourceEventDateRange,
+ eventType: EventLogEventTypes.EventLogShowSourceEventDateRange,
schema: {
isVisible: {
type: 'boolean',
@@ -35,3 +35,8 @@ export const eventLogShowSourceEventDateRangeEvent: EventLogTelemetryEvent = {
},
},
};
+
+export const eventLogTelemetryEvents = [
+ eventLogFilterByRunTypeEvent,
+ eventLogShowSourceEventDateRangeEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts
index b196c9010b258..a2a32290ce400 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts
@@ -5,25 +5,24 @@
* 2.0.
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export interface ReportEventLogFilterByRunTypeParams {
+export enum EventLogEventTypes {
+ EventLogFilterByRunType = 'Event Log Filter By Run Type',
+ EventLogShowSourceEventDateRange = 'Event Log -> Show Source -> Event Date Range',
+}
+interface ReportEventLogFilterByRunTypeParams {
runType: string[];
}
-export interface ReportEventLogShowSourceEventDateRangeParams {
+interface ReportEventLogShowSourceEventDateRangeParams {
isVisible: boolean;
}
-export type ReportEventLogTelemetryEventParams =
- | ReportEventLogFilterByRunTypeParams
- | ReportEventLogShowSourceEventDateRangeParams;
+export interface EventLogTelemetryEventsMap {
+ [EventLogEventTypes.EventLogFilterByRunType]: ReportEventLogFilterByRunTypeParams;
+ [EventLogEventTypes.EventLogShowSourceEventDateRange]: ReportEventLogShowSourceEventDateRangeParams;
+}
-export type EventLogTelemetryEvent =
- | {
- eventType: TelemetryEventTypes.EventLogFilterByRunType;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.EventLogShowSourceEventDateRange;
- schema: RootSchema;
- };
+export interface EventLogTelemetryEvent {
+ eventType: EventLogEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts
index a1476944d9806..3bc616dca1cf0 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { ManualRuleRunTelemetryEvent } from './types';
+import { ManualRuleRunEventTypes } from './types';
-export const manualRuleRunOpenModalEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.ManualRuleRunOpenModal,
+export const manualRuleRunOpenModalEvent: ManualRuleRunTelemetryEvent = {
+ eventType: ManualRuleRunEventTypes.ManualRuleRunOpenModal,
schema: {
type: {
type: 'keyword',
@@ -21,8 +21,8 @@ export const manualRuleRunOpenModalEvent: TelemetryEvent = {
},
};
-export const manualRuleRunExecuteEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.ManualRuleRunExecute,
+export const manualRuleRunExecuteEvent: ManualRuleRunTelemetryEvent = {
+ eventType: ManualRuleRunEventTypes.ManualRuleRunExecute,
schema: {
rangeInMs: {
type: 'integer',
@@ -50,8 +50,8 @@ export const manualRuleRunExecuteEvent: TelemetryEvent = {
},
};
-export const manualRuleRunCancelJobEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.ManualRuleRunCancelJob,
+export const manualRuleRunCancelJobEvent: ManualRuleRunTelemetryEvent = {
+ eventType: ManualRuleRunEventTypes.ManualRuleRunCancelJob,
schema: {
totalTasks: {
type: 'integer',
@@ -77,3 +77,9 @@ export const manualRuleRunCancelJobEvent: TelemetryEvent = {
},
},
};
+
+export const manualRuleRunTelemetryEvents = [
+ manualRuleRunCancelJobEvent,
+ manualRuleRunExecuteEvent,
+ manualRuleRunOpenModalEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts
index a58b0adf45503..231b555408e56 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts
@@ -5,39 +5,35 @@
* 2.0.
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export interface ReportManualRuleRunOpenModalParams {
+export enum ManualRuleRunEventTypes {
+ ManualRuleRunOpenModal = 'Manual Rule Run Open Modal',
+ ManualRuleRunExecute = 'Manual Rule Run Execute',
+ ManualRuleRunCancelJob = 'Manual Rule Run Cancel Job',
+}
+interface ReportManualRuleRunOpenModalParams {
type: 'single' | 'bulk';
}
-export interface ReportManualRuleRunExecuteParams {
+interface ReportManualRuleRunExecuteParams {
rangeInMs: number;
rulesCount: number;
status: 'success' | 'error';
}
-export interface ReportManualRuleRunCancelJobParams {
+interface ReportManualRuleRunCancelJobParams {
totalTasks: number;
completedTasks: number;
errorTasks: number;
}
-export type ReportManualRuleRunTelemetryEventParams =
- | ReportManualRuleRunOpenModalParams
- | ReportManualRuleRunExecuteParams
- | ReportManualRuleRunCancelJobParams;
+export interface ManualRuleRunTelemetryEventsMap {
+ [ManualRuleRunEventTypes.ManualRuleRunOpenModal]: ReportManualRuleRunOpenModalParams;
+ [ManualRuleRunEventTypes.ManualRuleRunExecute]: ReportManualRuleRunExecuteParams;
+ [ManualRuleRunEventTypes.ManualRuleRunCancelJob]: ReportManualRuleRunCancelJobParams;
+}
-export type ManualRuleRunTelemetryEvent =
- | {
- eventType: TelemetryEventTypes.ManualRuleRunOpenModal;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.ManualRuleRunExecute;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.ManualRuleRunCancelJob;
- schema: RootSchema;
- };
+export interface ManualRuleRunTelemetryEvent {
+ eventType: ManualRuleRunEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/index.ts
index c560f69730d36..94c9c350e0109 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { NotesTelemetryEvent } from './types';
+import { NotesEventTypes } from './types';
-export const openNoteInExpandableFlyoutClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.OpenNoteInExpandableFlyoutClicked,
+export const openNoteInExpandableFlyoutClickedEvent: NotesTelemetryEvent = {
+ eventType: NotesEventTypes.OpenNoteInExpandableFlyoutClicked,
schema: {
location: {
type: 'text',
@@ -21,8 +21,8 @@ export const openNoteInExpandableFlyoutClickedEvent: TelemetryEvent = {
},
};
-export const addNoteFromExpandableFlyoutClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AddNoteFromExpandableFlyoutClicked,
+export const addNoteFromExpandableFlyoutClickedEvent: NotesTelemetryEvent = {
+ eventType: NotesEventTypes.AddNoteFromExpandableFlyoutClicked,
schema: {
isRelatedToATimeline: {
type: 'boolean',
@@ -33,3 +33,8 @@ export const addNoteFromExpandableFlyoutClickedEvent: TelemetryEvent = {
},
},
};
+
+export const notesTelemetryEvents = [
+ openNoteInExpandableFlyoutClickedEvent,
+ addNoteFromExpandableFlyoutClickedEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/types.ts
index a785f2f8493e1..76440215c8079 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/notes/types.ts
@@ -6,26 +6,26 @@
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export interface OpenNoteInExpandableFlyoutClickedParams {
+interface OpenNoteInExpandableFlyoutClickedParams {
location: string;
}
-export interface AddNoteFromExpandableFlyoutClickedParams {
+interface AddNoteFromExpandableFlyoutClickedParams {
isRelatedToATimeline: boolean;
}
-export type NotesTelemetryEventParams =
- | OpenNoteInExpandableFlyoutClickedParams
- | AddNoteFromExpandableFlyoutClickedParams;
+export enum NotesEventTypes {
+ OpenNoteInExpandableFlyoutClicked = 'Open Note In Expandable Flyout Clicked',
+ AddNoteFromExpandableFlyoutClicked = 'Add Note From Expandable Flyout Clicked',
+}
+
+export interface NotesTelemetryEventsMap {
+ [NotesEventTypes.OpenNoteInExpandableFlyoutClicked]: OpenNoteInExpandableFlyoutClickedParams;
+ [NotesEventTypes.AddNoteFromExpandableFlyoutClicked]: AddNoteFromExpandableFlyoutClickedParams;
+}
-export type NotesTelemetryEvents =
- | {
- eventType: TelemetryEventTypes.OpenNoteInExpandableFlyoutClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AddNoteFromExpandableFlyoutClicked;
- schema: RootSchema;
- };
+export interface NotesTelemetryEvent {
+ eventType: NotesEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/index.ts
index dacb0c9483281..75a35e2d61c57 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { OnboardingHubTelemetryEvent } from './types';
+import { OnboardingHubEventTypes } from './types';
-export const onboardingHubStepOpenEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.OnboardingHubStepOpen,
+export const onboardingHubStepOpenEvent: OnboardingHubTelemetryEvent = {
+ eventType: OnboardingHubEventTypes.OnboardingHubStepOpen,
schema: {
stepId: {
type: 'keyword',
@@ -28,8 +28,8 @@ export const onboardingHubStepOpenEvent: TelemetryEvent = {
},
};
-export const onboardingHubStepLinkClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.OnboardingHubStepLinkClicked,
+export const onboardingHubStepLinkClickedEvent: OnboardingHubTelemetryEvent = {
+ eventType: OnboardingHubEventTypes.OnboardingHubStepLinkClicked,
schema: {
originStepId: {
type: 'keyword',
@@ -48,8 +48,8 @@ export const onboardingHubStepLinkClickedEvent: TelemetryEvent = {
},
};
-export const onboardingHubStepFinishedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.OnboardingHubStepFinished,
+export const onboardingHubStepFinishedEvent: OnboardingHubTelemetryEvent = {
+ eventType: OnboardingHubEventTypes.OnboardingHubStepFinished,
schema: {
stepId: {
type: 'keyword',
@@ -74,3 +74,9 @@ export const onboardingHubStepFinishedEvent: TelemetryEvent = {
},
},
};
+
+export const onboardingHubTelemetryEvents = [
+ onboardingHubStepOpenEvent,
+ onboardingHubStepLinkClickedEvent,
+ onboardingHubStepFinishedEvent,
+];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts
index 224635715b324..d11e9800e16fe 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts
@@ -5,30 +5,25 @@
* 2.0.
*/
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export type OnboardingHubStepOpenTrigger = 'navigation' | 'click';
+export enum OnboardingHubEventTypes {
+ OnboardingHubStepOpen = 'Onboarding Hub Step Open',
+ OnboardingHubStepFinished = 'Onboarding Hub Step Finished',
+ OnboardingHubStepLinkClicked = 'Onboarding Hub Step Link Clicked',
+}
+
+type OnboardingHubStepOpenTrigger = 'navigation' | 'click';
-export interface OnboardingHubStepOpenParams {
+interface OnboardingHubStepOpenParams {
stepId: string;
trigger: OnboardingHubStepOpenTrigger;
}
-export interface OnboardingHubStepOpen {
- eventType: TelemetryEventTypes.OnboardingHubStepOpen;
- schema: RootSchema;
-}
-
export interface OnboardingHubStepLinkClickedParams {
originStepId: string;
stepLinkId: string;
}
-export interface OnboardingHubStepLinkClicked {
- eventType: TelemetryEventTypes.OnboardingHubStepLinkClicked;
- schema: RootSchema;
-}
-
export type OnboardingHubStepFinishedTrigger = 'auto_check' | 'click';
export interface OnboardingHubStepFinishedParams {
@@ -37,12 +32,13 @@ export interface OnboardingHubStepFinishedParams {
trigger: OnboardingHubStepFinishedTrigger;
}
-export interface OnboardingHubStepFinished {
- eventType: TelemetryEventTypes.OnboardingHubStepFinished;
- schema: RootSchema;
+export interface OnboardingHubTelemetryEventsMap {
+ [OnboardingHubEventTypes.OnboardingHubStepOpen]: OnboardingHubStepOpenParams;
+ [OnboardingHubEventTypes.OnboardingHubStepFinished]: OnboardingHubStepFinishedParams;
+ [OnboardingHubEventTypes.OnboardingHubStepLinkClicked]: OnboardingHubStepLinkClickedParams;
}
-export type OnboardingHubTelemetryEvent =
- | OnboardingHubStepOpen
- | OnboardingHubStepFinished
- | OnboardingHubStepLinkClicked;
+export interface OnboardingHubTelemetryEvent {
+ eventType: OnboardingHubEventTypes;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/index.ts
index 12d721c45e2c0..f34380935b0ed 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/index.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import type { TelemetryEvent } from '../../types';
-import { TelemetryEventTypes } from '../../constants';
+import type { PreviewRuleTelemetryEvent } from './types';
+import { PreviewRuleEventTypes } from './types';
-export const previewRuleEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.PreviewRule,
+export const previewRuleEvent: PreviewRuleTelemetryEvent = {
+ eventType: PreviewRuleEventTypes.PreviewRule,
schema: {
ruleType: {
type: 'keyword',
@@ -27,3 +27,5 @@ export const previewRuleEvent: TelemetryEvent = {
},
},
};
+
+export const previewRuleTelemetryEvents = [previewRuleEvent];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/types.ts
index e5523080088fc..876378e24553b 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/preview_rule/types.ts
@@ -7,14 +7,21 @@
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import type { RootSchema } from '@kbn/core/public';
-import type { TelemetryEventTypes } from '../../constants';
-export interface PreviewRuleParams {
+interface PreviewRuleParams {
ruleType: Type;
loggedRequestsEnabled: boolean;
}
+export enum PreviewRuleEventTypes {
+ PreviewRule = 'Preview rule',
+}
+
+export interface PreviewRuleTelemetryEventsMap {
+ [PreviewRuleEventTypes.PreviewRule]: PreviewRuleParams;
+}
+
export interface PreviewRuleTelemetryEvent {
- eventType: TelemetryEventTypes.PreviewRule;
- schema: RootSchema;
+ eventType: PreviewRuleEventTypes;
+ schema: RootSchema;
}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts
index 3e7c9f1138391..b610f6e77dda1 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts
@@ -4,198 +4,28 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import type { TelemetryEvent } from '../types';
-import { TelemetryEventTypes } from '../constants';
-import {
- alertsGroupingChangedEvent,
- alertsGroupingTakeActionEvent,
- alertsGroupingToggledEvent,
-} from './alerts_grouping';
-import {
- entityAlertsClickedEvent,
- entityClickedEvent,
- entityRiskFilteredEvent,
- addRiskInputToTimelineClickedEvent,
- RiskInputsExpandedFlyoutOpenedEvent,
- toggleRiskSummaryClickedEvent,
- assetCriticalityCsvPreviewGeneratedEvent,
- assetCriticalityFileSelectedEvent,
- assetCriticalityCsvImportedEvent,
- entityStoreEnablementEvent,
- entityStoreInitEvent,
-} from './entity_analytics';
-import {
- assistantInvokedEvent,
- assistantSettingToggledEvent,
- assistantMessageSentEvent,
- assistantQuickPrompt,
-} from './ai_assistant';
-import { dataQualityIndexCheckedEvent, dataQualityCheckAllClickedEvent } from './data_quality';
-import {
- DocumentDetailsFlyoutOpenedEvent,
- DocumentDetailsTabClickedEvent,
-} from './document_details';
-import {
- onboardingHubStepFinishedEvent,
- onboardingHubStepLinkClickedEvent,
- onboardingHubStepOpenEvent,
-} from './onboarding';
-import {
- manualRuleRunCancelJobEvent,
- manualRuleRunExecuteEvent,
- manualRuleRunOpenModalEvent,
-} from './manual_rule_run';
-import { eventLogFilterByRunTypeEvent, eventLogShowSourceEventDateRangeEvent } from './event_log';
-import {
- addNoteFromExpandableFlyoutClickedEvent,
- openNoteInExpandableFlyoutClickedEvent,
-} from './notes';
-import { previewRuleEvent } from './preview_rule';
-
-const mlJobUpdateEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.MLJobUpdate,
- schema: {
- jobId: {
- type: 'keyword',
- _meta: {
- description: 'Job id',
- optional: false,
- },
- },
- isElasticJob: {
- type: 'boolean',
- _meta: {
- description: 'If true the job is one of the pre-configure security solution modules',
- optional: false,
- },
- },
- moduleId: {
- type: 'keyword',
- _meta: {
- description: 'Module id',
- optional: true,
- },
- },
- status: {
- type: 'keyword',
- _meta: {
- description: 'It describes what has changed in the job.',
- optional: false,
- },
- },
- errorMessage: {
- type: 'text',
- _meta: {
- description: 'Error message',
- optional: true,
- },
- },
- },
-};
-
-const cellActionClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.CellActionClicked,
- schema: {
- fieldName: {
- type: 'keyword',
- _meta: {
- description: 'Field Name',
- optional: false,
- },
- },
- actionId: {
- type: 'keyword',
- _meta: {
- description: 'Action id',
- optional: false,
- },
- },
- displayName: {
- type: 'keyword',
- _meta: {
- description: 'User friendly action name',
- optional: false,
- },
- },
- metadata: {
- type: 'pass_through',
- _meta: {
- description: 'Action metadata',
- optional: true,
- },
- },
- },
-};
-
-const anomaliesCountClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.AnomaliesCountClicked,
- schema: {
- jobId: {
- type: 'keyword',
- _meta: {
- description: 'Job id',
- optional: false,
- },
- },
- count: {
- type: 'integer',
- _meta: {
- description: 'Number of anomalies',
- optional: false,
- },
- },
- },
-};
-
-const breadCrumbClickedEvent: TelemetryEvent = {
- eventType: TelemetryEventTypes.BreadcrumbClicked,
- schema: {
- title: {
- type: 'keyword',
- _meta: {
- description: 'Breadcrumb title',
- optional: false,
- },
- },
- },
-};
+import { assistantTelemetryEvents } from './ai_assistant';
+import { alertsTelemetryEvents } from './alerts_grouping';
+import { appTelemetryEvents } from './app';
+import { dataQualityTelemetryEvents } from './data_quality';
+import { documentTelemetryEvents } from './document_details';
+import { entityTelemetryEvents } from './entity_analytics';
+import { eventLogTelemetryEvents } from './event_log';
+import { manualRuleRunTelemetryEvents } from './manual_rule_run';
+import { notesTelemetryEvents } from './notes';
+import { onboardingHubTelemetryEvents } from './onboarding';
+import { previewRuleTelemetryEvents } from './preview_rule';
export const telemetryEvents = [
- alertsGroupingToggledEvent,
- alertsGroupingChangedEvent,
- alertsGroupingTakeActionEvent,
- assistantInvokedEvent,
- assistantMessageSentEvent,
- assistantQuickPrompt,
- assistantSettingToggledEvent,
- entityClickedEvent,
- entityAlertsClickedEvent,
- entityRiskFilteredEvent,
- assetCriticalityCsvPreviewGeneratedEvent,
- assetCriticalityFileSelectedEvent,
- assetCriticalityCsvImportedEvent,
- entityStoreEnablementEvent,
- entityStoreInitEvent,
- toggleRiskSummaryClickedEvent,
- RiskInputsExpandedFlyoutOpenedEvent,
- addRiskInputToTimelineClickedEvent,
- mlJobUpdateEvent,
- cellActionClickedEvent,
- anomaliesCountClickedEvent,
- dataQualityIndexCheckedEvent,
- dataQualityCheckAllClickedEvent,
- breadCrumbClickedEvent,
- DocumentDetailsFlyoutOpenedEvent,
- DocumentDetailsTabClickedEvent,
- onboardingHubStepOpenEvent,
- onboardingHubStepLinkClickedEvent,
- onboardingHubStepFinishedEvent,
- manualRuleRunCancelJobEvent,
- manualRuleRunExecuteEvent,
- manualRuleRunOpenModalEvent,
- eventLogFilterByRunTypeEvent,
- eventLogShowSourceEventDateRangeEvent,
- openNoteInExpandableFlyoutClickedEvent,
- addNoteFromExpandableFlyoutClickedEvent,
- previewRuleEvent,
+ ...assistantTelemetryEvents,
+ ...alertsTelemetryEvents,
+ ...previewRuleTelemetryEvents,
+ ...entityTelemetryEvents,
+ ...dataQualityTelemetryEvents,
+ ...documentTelemetryEvents,
+ ...onboardingHubTelemetryEvents,
+ ...manualRuleRunTelemetryEvents,
+ ...eventLogTelemetryEvents,
+ ...notesTelemetryEvents,
+ ...appTelemetryEvents,
];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/index.ts
index 5a6818c712de5..a8df452d512a0 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/index.ts
@@ -5,23 +5,9 @@
* 2.0.
*/
-import type { AlertWorkflowStatus } from '../../types';
export { telemetryMiddleware } from './middleware';
export * from './constants';
-export * from './telemetry_client';
export * from './telemetry_service';
export * from './track';
export * from './types';
-
-export const getTelemetryEvent = {
- groupedAlertsTakeAction: ({
- tableId,
- groupNumber,
- status,
- }: {
- tableId: string;
- groupNumber: number;
- status: AlertWorkflowStatus;
- }) => `alerts_table_${tableId}_group-${groupNumber}_mark-${status}`,
-};
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts
deleted file mode 100644
index 87d4b215543dc..0000000000000
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts
+++ /dev/null
@@ -1,48 +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 type { TelemetryClientStart } from './types';
-
-export const createTelemetryClientMock = (): jest.Mocked => ({
- reportAlertsGroupingChanged: jest.fn(),
- reportAlertsGroupingToggled: jest.fn(),
- reportAlertsGroupingTakeAction: jest.fn(),
- reportAssistantInvoked: jest.fn(),
- reportAssistantMessageSent: jest.fn(),
- reportAssistantQuickPrompt: jest.fn(),
- reportAssistantSettingToggled: jest.fn(),
- reportEntityDetailsClicked: jest.fn(),
- reportEntityAlertsClicked: jest.fn(),
- reportEntityRiskFiltered: jest.fn(),
- reportMLJobUpdate: jest.fn(),
- reportCellActionClicked: jest.fn(),
- reportAnomaliesCountClicked: jest.fn(),
- reportDataQualityIndexChecked: jest.fn(),
- reportDataQualityCheckAllCompleted: jest.fn(),
- reportBreadcrumbClicked: jest.fn(),
- reportToggleRiskSummaryClicked: jest.fn(),
- reportRiskInputsExpandedFlyoutOpened: jest.fn(),
- reportAddRiskInputToTimelineClicked: jest.fn(),
- reportDetailsFlyoutOpened: jest.fn(),
- reportDetailsFlyoutTabClicked: jest.fn(),
- reportOnboardingHubStepOpen: jest.fn(),
- reportOnboardingHubStepLinkClicked: jest.fn(),
- reportOnboardingHubStepFinished: jest.fn(),
- reportAssetCriticalityCsvPreviewGenerated: jest.fn(),
- reportAssetCriticalityFileSelected: jest.fn(),
- reportAssetCriticalityCsvImported: jest.fn(),
- reportEventLogFilterByRunType: jest.fn(),
- reportEventLogShowSourceEventDateRange: jest.fn(),
- reportManualRuleRunCancelJob: jest.fn(),
- reportManualRuleRunExecute: jest.fn(),
- reportManualRuleRunOpenModal: jest.fn(),
- reportOpenNoteInExpandableFlyoutClicked: jest.fn(),
- reportAddNoteFromExpandableFlyoutClicked: jest.fn(),
- reportPreviewRule: jest.fn(),
- reportEntityStoreEnablement: jest.fn(),
- reportEntityStoreInit: jest.fn(),
-});
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts
deleted file mode 100644
index 689209f284dbb..0000000000000
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts
+++ /dev/null
@@ -1,229 +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 type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
-import type {
- AddNoteFromExpandableFlyoutClickedParams,
- OpenNoteInExpandableFlyoutClickedParams,
-} from './events/notes/types';
-import type {
- TelemetryClientStart,
- ReportAlertsGroupingChangedParams,
- ReportAlertsGroupingToggledParams,
- ReportAlertsTakeActionParams,
- ReportEntityDetailsClickedParams,
- ReportEntityAlertsClickedParams,
- ReportEntityRiskFilteredParams,
- ReportMLJobUpdateParams,
- ReportCellActionClickedParams,
- ReportAnomaliesCountClickedParams,
- ReportDataQualityIndexCheckedParams,
- ReportDataQualityCheckAllCompletedParams,
- ReportBreadcrumbClickedParams,
- ReportAssistantInvokedParams,
- ReportAssistantMessageSentParams,
- ReportAssistantQuickPromptParams,
- ReportAssistantSettingToggledParams,
- ReportRiskInputsExpandedFlyoutOpenedParams,
- ReportToggleRiskSummaryClickedParams,
- ReportDetailsFlyoutOpenedParams,
- ReportDetailsFlyoutTabClickedParams,
- ReportAssetCriticalityCsvPreviewGeneratedParams,
- ReportAssetCriticalityFileSelectedParams,
- ReportAssetCriticalityCsvImportedParams,
- ReportAddRiskInputToTimelineClickedParams,
- OnboardingHubStepLinkClickedParams,
- OnboardingHubStepOpenParams,
- OnboardingHubStepFinishedParams,
- ReportManualRuleRunCancelJobParams,
- ReportManualRuleRunExecuteParams,
- ReportManualRuleRunOpenModalParams,
- ReportEventLogShowSourceEventDateRangeParams,
- ReportEventLogFilterByRunTypeParams,
- PreviewRuleParams,
- ReportEntityStoreEnablementParams,
- ReportEntityStoreInitParams,
-} from './types';
-import { TelemetryEventTypes } from './constants';
-
-/**
- * Client which aggregate all the available telemetry tracking functions
- * for the plugin
- */
-export class TelemetryClient implements TelemetryClientStart {
- constructor(private analytics: AnalyticsServiceSetup) {}
-
- public reportAlertsGroupingChanged = (params: ReportAlertsGroupingChangedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AlertsGroupingChanged, params);
- };
-
- public reportAlertsGroupingToggled = (params: ReportAlertsGroupingToggledParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AlertsGroupingToggled, params);
- };
-
- public reportAlertsGroupingTakeAction = (params: ReportAlertsTakeActionParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AlertsGroupingTakeAction, params);
- };
-
- public reportAssistantInvoked = (params: ReportAssistantInvokedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssistantInvoked, params);
- };
-
- public reportAssistantMessageSent = (params: ReportAssistantMessageSentParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssistantMessageSent, params);
- };
-
- public reportAssistantQuickPrompt = (params: ReportAssistantQuickPromptParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssistantQuickPrompt, params);
- };
-
- public reportAssistantSettingToggled = (params: ReportAssistantSettingToggledParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssistantSettingToggled, params);
- };
-
- public reportEntityDetailsClicked = ({ entity }: ReportEntityDetailsClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.EntityDetailsClicked, {
- entity,
- });
- };
-
- public reportEntityAlertsClicked = ({ entity }: ReportEntityAlertsClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.EntityAlertsClicked, {
- entity,
- });
- };
-
- public reportEntityRiskFiltered = ({
- entity,
- selectedSeverity,
- }: ReportEntityRiskFilteredParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.EntityRiskFiltered, {
- entity,
- selectedSeverity,
- });
- };
-
- public reportAssetCriticalityCsvPreviewGenerated = (
- params: ReportAssetCriticalityCsvPreviewGeneratedParams
- ) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssetCriticalityCsvPreviewGenerated, params);
- };
-
- public reportAssetCriticalityFileSelected = (
- params: ReportAssetCriticalityFileSelectedParams
- ) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssetCriticalityFileSelected, params);
- };
-
- public reportAssetCriticalityCsvImported = (params: ReportAssetCriticalityCsvImportedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AssetCriticalityCsvImported, params);
- };
-
- public reportMLJobUpdate = (params: ReportMLJobUpdateParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.MLJobUpdate, params);
- };
-
- reportToggleRiskSummaryClicked(params: ReportToggleRiskSummaryClickedParams): void {
- this.analytics.reportEvent(TelemetryEventTypes.ToggleRiskSummaryClicked, params);
- }
- reportRiskInputsExpandedFlyoutOpened(params: ReportRiskInputsExpandedFlyoutOpenedParams): void {
- this.analytics.reportEvent(TelemetryEventTypes.RiskInputsExpandedFlyoutOpened, params);
- }
- reportAddRiskInputToTimelineClicked(params: ReportAddRiskInputToTimelineClickedParams): void {
- this.analytics.reportEvent(TelemetryEventTypes.AddRiskInputToTimelineClicked, params);
- }
-
- public reportCellActionClicked = (params: ReportCellActionClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.CellActionClicked, params);
- };
-
- public reportAnomaliesCountClicked = (params: ReportAnomaliesCountClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.AnomaliesCountClicked, params);
- };
-
- public reportDataQualityIndexChecked = (params: ReportDataQualityIndexCheckedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.DataQualityIndexChecked, params);
- };
-
- public reportDataQualityCheckAllCompleted = (
- params: ReportDataQualityCheckAllCompletedParams
- ) => {
- this.analytics.reportEvent(TelemetryEventTypes.DataQualityCheckAllCompleted, params);
- };
-
- public reportBreadcrumbClicked = ({ title }: ReportBreadcrumbClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.BreadcrumbClicked, {
- title,
- });
- };
-
- public reportDetailsFlyoutOpened = (params: ReportDetailsFlyoutOpenedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.DetailsFlyoutOpened, params);
- };
-
- public reportDetailsFlyoutTabClicked = (params: ReportDetailsFlyoutTabClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.DetailsFlyoutTabClicked, params);
- };
-
- public reportOnboardingHubStepOpen = (params: OnboardingHubStepOpenParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.OnboardingHubStepOpen, params);
- };
-
- public reportOnboardingHubStepFinished = (params: OnboardingHubStepFinishedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.OnboardingHubStepFinished, params);
- };
-
- public reportOnboardingHubStepLinkClicked = (params: OnboardingHubStepLinkClickedParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.OnboardingHubStepLinkClicked, params);
- };
-
- public reportManualRuleRunOpenModal = (params: ReportManualRuleRunOpenModalParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.ManualRuleRunOpenModal, params);
- };
-
- public reportManualRuleRunExecute = (params: ReportManualRuleRunExecuteParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.ManualRuleRunExecute, params);
- };
-
- public reportManualRuleRunCancelJob = (params: ReportManualRuleRunCancelJobParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.ManualRuleRunCancelJob, params);
- };
-
- public reportEventLogFilterByRunType = (params: ReportEventLogFilterByRunTypeParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.EventLogFilterByRunType, params);
- };
-
- public reportEventLogShowSourceEventDateRange(
- params: ReportEventLogShowSourceEventDateRangeParams
- ): void {
- this.analytics.reportEvent(TelemetryEventTypes.EventLogShowSourceEventDateRange, params);
- }
-
- public reportOpenNoteInExpandableFlyoutClicked = (
- params: OpenNoteInExpandableFlyoutClickedParams
- ) => {
- this.analytics.reportEvent(TelemetryEventTypes.OpenNoteInExpandableFlyoutClicked, params);
- };
-
- public reportAddNoteFromExpandableFlyoutClicked = (
- params: AddNoteFromExpandableFlyoutClickedParams
- ) => {
- this.analytics.reportEvent(TelemetryEventTypes.AddNoteFromExpandableFlyoutClicked, params);
- };
-
- public reportPreviewRule = (params: PreviewRuleParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.PreviewRule, params);
- };
-
- public reportEntityStoreEnablement = (params: ReportEntityStoreEnablementParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.EntityStoreEnablementToggleClicked, params);
- };
-
- public reportEntityStoreInit = (params: ReportEntityStoreInitParams) => {
- this.analytics.reportEvent(TelemetryEventTypes.EntityStoreDashboardInitButtonClicked, params);
- };
-}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.mock.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.mock.ts
index 519ba4527560b..30b8a0c434c5f 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.mock.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.mock.ts
@@ -5,6 +5,4 @@
* 2.0.
*/
-import { createTelemetryClientMock } from './telemetry_client.mock';
-
-export const createTelemetryServiceMock = () => createTelemetryClientMock();
+export const createTelemetryServiceMock = () => ({ reportEvent: jest.fn() });
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts
index 9079c6bf4f650..486aa241290a8 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts
@@ -8,7 +8,7 @@ import { coreMock } from '@kbn/core/server/mocks';
import { telemetryEvents } from './events/telemetry_events';
import { TelemetryService } from './telemetry_service';
-import { TelemetryEventTypes } from './constants';
+import { AlertsEventTypes } from './types';
describe('TelemetryService', () => {
let service: TelemetryService;
@@ -41,17 +41,12 @@ describe('TelemetryService', () => {
});
describe('#start()', () => {
- it('should return all the available tracking methods', () => {
+ it('should return the tracking method', () => {
const setupParams = getSetupParams();
service.setup(setupParams);
const telemetry = service.start();
- expect(telemetry).toHaveProperty('reportAlertsGroupingChanged');
- expect(telemetry).toHaveProperty('reportAlertsGroupingToggled');
- expect(telemetry).toHaveProperty('reportAlertsGroupingTakeAction');
-
- expect(telemetry).toHaveProperty('reportDetailsFlyoutOpened');
- expect(telemetry).toHaveProperty('reportDetailsFlyoutTabClicked');
+ expect(telemetry).toHaveProperty('reportEvent');
});
});
@@ -61,7 +56,7 @@ describe('TelemetryService', () => {
service.setup(setupParams);
const telemetry = service.start();
- telemetry.reportAlertsGroupingTakeAction({
+ telemetry.reportEvent(AlertsEventTypes.AlertsGroupingTakeAction, {
tableId: 'test-groupingId',
groupNumber: 0,
status: 'closed',
@@ -70,7 +65,7 @@ describe('TelemetryService', () => {
expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1);
expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith(
- TelemetryEventTypes.AlertsGroupingTakeAction,
+ AlertsEventTypes.AlertsGroupingTakeAction,
{
tableId: 'test-groupingId',
groupNumber: 0,
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts
index d4c100d5fe407..a1bf49394af9e 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts
@@ -8,13 +8,18 @@ import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
import { of } from 'rxjs';
import type {
+ TelemetryEventTypeData,
+ TelemetryEventTypes,
TelemetryServiceSetupParams,
- TelemetryClientStart,
- TelemetryEventParams,
} from './types';
import { telemetryEvents } from './events/telemetry_events';
-import { TelemetryClient } from './telemetry_client';
+export interface TelemetryServiceStart {
+ reportEvent: (
+ eventType: T,
+ eventData: TelemetryEventTypeData
+ ) => void;
+}
/**
* Service that interacts with the Core's analytics module
* to trigger custom event for Security Solution plugin features
@@ -41,17 +46,19 @@ export class TelemetryService {
});
}
telemetryEvents.forEach((eventConfig) =>
- analytics.registerEventType(eventConfig)
+ analytics.registerEventType>(eventConfig)
);
}
- public start(): TelemetryClientStart {
- if (!this.analytics) {
+ public start(): TelemetryServiceStart {
+ const reportEvent = this.analytics?.reportEvent.bind(this.analytics);
+
+ if (!this.analytics || !reportEvent) {
throw new Error(
'The TelemetryService.setup() method has not been invoked, be sure to call it during the plugin setup.'
);
}
- return new TelemetryClient(this.analytics);
+ return { reportEvent };
}
}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts
index 95896bf74a6a7..9cd56ebcb60f4 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts
@@ -5,77 +5,41 @@
* 2.0.
*/
-import type { AnalyticsServiceSetup, RootSchema } from '@kbn/core/public';
-import type { SecurityCellActionMetadata } from '../../../app/actions/types';
-import type { ML_JOB_TELEMETRY_STATUS, TelemetryEventTypes } from './constants';
+import type { AnalyticsServiceSetup } from '@kbn/core/public';
import type {
- AlertsGroupingTelemetryEvent,
- ReportAlertsGroupingChangedParams,
- ReportAlertsGroupingTelemetryEventParams,
- ReportAlertsGroupingToggledParams,
- ReportAlertsTakeActionParams,
+ AlertsEventTypes,
+ AlertsGroupingTelemetryEventsMap,
} from './events/alerts_grouping/types';
import type {
- ReportDataQualityCheckAllCompletedParams,
- ReportDataQualityIndexCheckedParams,
- DataQualityTelemetryEvents,
+ DataQualityEventTypes,
+ DataQualityTelemetryEventsMap,
} from './events/data_quality/types';
import type {
- EntityAnalyticsTelemetryEvent,
- ReportAddRiskInputToTimelineClickedParams,
- ReportEntityAlertsClickedParams,
- ReportEntityAnalyticsTelemetryEventParams,
- ReportEntityDetailsClickedParams,
- ReportEntityRiskFilteredParams,
- ReportRiskInputsExpandedFlyoutOpenedParams,
- ReportToggleRiskSummaryClickedParams,
- ReportAssetCriticalityCsvPreviewGeneratedParams,
- ReportAssetCriticalityFileSelectedParams,
- ReportAssetCriticalityCsvImportedParams,
- ReportEntityStoreEnablementParams,
- ReportEntityStoreInitParams,
+ EntityAnalyticsTelemetryEventsMap,
+ EntityEventTypes,
} from './events/entity_analytics/types';
+import type { AssistantEventTypes, AssistantTelemetryEventsMap } from './events/ai_assistant/types';
import type {
- AssistantTelemetryEvent,
- ReportAssistantTelemetryEventParams,
- ReportAssistantInvokedParams,
- ReportAssistantQuickPromptParams,
- ReportAssistantMessageSentParams,
- ReportAssistantSettingToggledParams,
-} from './events/ai_assistant/types';
-import type {
- DocumentDetailsTelemetryEvents,
- ReportDocumentDetailsTelemetryEventParams,
- ReportDetailsFlyoutOpenedParams,
- ReportDetailsFlyoutTabClickedParams,
+ DocumentDetailsTelemetryEventsMap,
+ DocumentEventTypes,
} from './events/document_details/types';
import type {
- OnboardingHubStepFinishedParams,
- OnboardingHubStepLinkClickedParams,
- OnboardingHubStepOpenParams,
- OnboardingHubTelemetryEvent,
+ OnboardingHubEventTypes,
+ OnboardingHubTelemetryEventsMap,
} from './events/onboarding/types';
import type {
- ManualRuleRunTelemetryEvent,
- ReportManualRuleRunOpenModalParams,
- ReportManualRuleRunExecuteParams,
- ReportManualRuleRunCancelJobParams,
- ReportManualRuleRunTelemetryEventParams,
+ ManualRuleRunEventTypes,
+ ManualRuleRunTelemetryEventsMap,
} from './events/manual_rule_run/types';
+import type { EventLogEventTypes, EventLogTelemetryEventsMap } from './events/event_log/types';
+import type { NotesEventTypes, NotesTelemetryEventsMap } from './events/notes/types';
import type {
- EventLogTelemetryEvent,
- ReportEventLogFilterByRunTypeParams,
- ReportEventLogShowSourceEventDateRangeParams,
- ReportEventLogTelemetryEventParams,
-} from './events/event_log/types';
-import type {
- AddNoteFromExpandableFlyoutClickedParams,
- NotesTelemetryEventParams,
- NotesTelemetryEvents,
- OpenNoteInExpandableFlyoutClickedParams,
-} from './events/notes/types';
-import type { PreviewRuleParams, PreviewRuleTelemetryEvent } from './events/preview_rule/types';
+ PreviewRuleEventTypes,
+ PreviewRuleTelemetryEventsMap,
+} from './events/preview_rule/types';
+import type { AppEventTypes, AppTelemetryEventsMap } from './events/app/types';
+export * from './events/app/types';
export * from './events/ai_assistant/types';
export * from './events/alerts_grouping/types';
export * from './events/data_quality/types';
@@ -85,142 +49,46 @@ export * from './events/document_details/types';
export * from './events/manual_rule_run/types';
export * from './events/event_log/types';
export * from './events/preview_rule/types';
+export * from './events/notes/types';
export interface TelemetryServiceSetupParams {
analytics: AnalyticsServiceSetup;
}
-export interface ReportMLJobUpdateParams {
- jobId: string;
- isElasticJob: boolean;
- status: ML_JOB_TELEMETRY_STATUS;
- moduleId?: string;
- errorMessage?: string;
-}
-
-export interface ReportCellActionClickedParams {
- metadata: SecurityCellActionMetadata | undefined;
- displayName: string;
- actionId: string;
- fieldName: string;
-}
-
-export interface ReportAnomaliesCountClickedParams {
- jobId: string;
- count: number;
-}
-
-export interface ReportBreadcrumbClickedParams {
- title: string;
-}
-
-export type TelemetryEventParams =
- | ReportAlertsGroupingTelemetryEventParams
- | ReportAssistantTelemetryEventParams
- | ReportEntityAnalyticsTelemetryEventParams
- | ReportMLJobUpdateParams
- | ReportCellActionClickedParams
- | ReportAnomaliesCountClickedParams
- | ReportDataQualityIndexCheckedParams
- | ReportDataQualityCheckAllCompletedParams
- | ReportBreadcrumbClickedParams
- | ReportDocumentDetailsTelemetryEventParams
- | OnboardingHubStepOpenParams
- | OnboardingHubStepFinishedParams
- | OnboardingHubStepLinkClickedParams
- | ReportManualRuleRunTelemetryEventParams
- | ReportEventLogTelemetryEventParams
- | PreviewRuleParams
- | NotesTelemetryEventParams;
-
-export interface TelemetryClientStart {
- reportAlertsGroupingChanged(params: ReportAlertsGroupingChangedParams): void;
- reportAlertsGroupingToggled(params: ReportAlertsGroupingToggledParams): void;
- reportAlertsGroupingTakeAction(params: ReportAlertsTakeActionParams): void;
-
- // Assistant
- reportAssistantInvoked(params: ReportAssistantInvokedParams): void;
- reportAssistantMessageSent(params: ReportAssistantMessageSentParams): void;
- reportAssistantQuickPrompt(params: ReportAssistantQuickPromptParams): void;
- reportAssistantSettingToggled(params: ReportAssistantSettingToggledParams): void;
-
- // Entity Analytics
- reportEntityDetailsClicked(params: ReportEntityDetailsClickedParams): void;
- reportEntityAlertsClicked(params: ReportEntityAlertsClickedParams): void;
- reportEntityRiskFiltered(params: ReportEntityRiskFilteredParams): void;
- reportMLJobUpdate(params: ReportMLJobUpdateParams): void;
- // Entity Analytics inside Entity Flyout
- reportToggleRiskSummaryClicked(params: ReportToggleRiskSummaryClickedParams): void;
- reportRiskInputsExpandedFlyoutOpened(params: ReportRiskInputsExpandedFlyoutOpenedParams): void;
- reportAddRiskInputToTimelineClicked(params: ReportAddRiskInputToTimelineClickedParams): void;
- // Entity Analytics Asset Criticality
- reportAssetCriticalityFileSelected(params: ReportAssetCriticalityFileSelectedParams): void;
- reportAssetCriticalityCsvPreviewGenerated(
- params: ReportAssetCriticalityCsvPreviewGeneratedParams
- ): void;
- reportAssetCriticalityCsvImported(params: ReportAssetCriticalityCsvImportedParams): void;
- reportCellActionClicked(params: ReportCellActionClickedParams): void;
- // Entity Analytics Entity Store
- reportEntityStoreEnablement(params: ReportEntityStoreEnablementParams): void;
- reportEntityStoreInit(params: ReportEntityStoreInitParams): void;
-
- reportAnomaliesCountClicked(params: ReportAnomaliesCountClickedParams): void;
- reportDataQualityIndexChecked(params: ReportDataQualityIndexCheckedParams): void;
- reportDataQualityCheckAllCompleted(params: ReportDataQualityCheckAllCompletedParams): void;
- reportBreadcrumbClicked(params: ReportBreadcrumbClickedParams): void;
-
- // document details flyout
- reportDetailsFlyoutOpened(params: ReportDetailsFlyoutOpenedParams): void;
- reportDetailsFlyoutTabClicked(params: ReportDetailsFlyoutTabClickedParams): void;
-
- // onboarding hub
- reportOnboardingHubStepOpen(params: OnboardingHubStepOpenParams): void;
- reportOnboardingHubStepFinished(params: OnboardingHubStepFinishedParams): void;
- reportOnboardingHubStepLinkClicked(params: OnboardingHubStepLinkClickedParams): void;
-
- // manual rule run
- reportManualRuleRunOpenModal(params: ReportManualRuleRunOpenModalParams): void;
- reportManualRuleRunExecute(params: ReportManualRuleRunExecuteParams): void;
- reportManualRuleRunCancelJob(params: ReportManualRuleRunCancelJobParams): void;
-
- // event log
- reportEventLogFilterByRunType(params: ReportEventLogFilterByRunTypeParams): void;
- reportEventLogShowSourceEventDateRange(
- params: ReportEventLogShowSourceEventDateRangeParams
- ): void;
-
- // new notes
- reportOpenNoteInExpandableFlyoutClicked(params: OpenNoteInExpandableFlyoutClickedParams): void;
- reportAddNoteFromExpandableFlyoutClicked(params: AddNoteFromExpandableFlyoutClickedParams): void;
-
- // preview rule
- reportPreviewRule(params: PreviewRuleParams): void;
-}
-
-export type TelemetryEvent =
- | AssistantTelemetryEvent
- | AlertsGroupingTelemetryEvent
- | EntityAnalyticsTelemetryEvent
- | DataQualityTelemetryEvents
- | DocumentDetailsTelemetryEvents
- | {
- eventType: TelemetryEventTypes.MLJobUpdate;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.CellActionClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.AnomaliesCountClicked;
- schema: RootSchema;
- }
- | {
- eventType: TelemetryEventTypes.BreadcrumbClicked;
- schema: RootSchema;
- }
- | OnboardingHubTelemetryEvent
- | ManualRuleRunTelemetryEvent
- | EventLogTelemetryEvent
- | PreviewRuleTelemetryEvent
- | NotesTelemetryEvents;
+// Combine all event type data
+export type TelemetryEventTypeData = T extends AssistantEventTypes
+ ? AssistantTelemetryEventsMap[T]
+ : T extends AlertsEventTypes
+ ? AlertsGroupingTelemetryEventsMap[T]
+ : T extends PreviewRuleEventTypes
+ ? PreviewRuleTelemetryEventsMap[T]
+ : T extends EntityEventTypes
+ ? EntityAnalyticsTelemetryEventsMap[T]
+ : T extends DataQualityEventTypes
+ ? DataQualityTelemetryEventsMap[T]
+ : T extends DocumentEventTypes
+ ? DocumentDetailsTelemetryEventsMap[T]
+ : T extends OnboardingHubEventTypes
+ ? OnboardingHubTelemetryEventsMap[T]
+ : T extends ManualRuleRunEventTypes
+ ? ManualRuleRunTelemetryEventsMap[T]
+ : T extends EventLogEventTypes
+ ? EventLogTelemetryEventsMap[T]
+ : T extends NotesEventTypes
+ ? NotesTelemetryEventsMap[T]
+ : T extends AppEventTypes
+ ? AppTelemetryEventsMap[T]
+ : never;
+
+export type TelemetryEventTypes =
+ | AssistantEventTypes
+ | AlertsEventTypes
+ | PreviewRuleEventTypes
+ | EntityEventTypes
+ | DataQualityEventTypes
+ | DocumentEventTypes
+ | OnboardingHubEventTypes
+ | ManualRuleRunEventTypes
+ | EventLogEventTypes
+ | NotesEventTypes
+ | AppEventTypes;
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts
index 018e2602aa170..79257b4fef3da 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts
@@ -18,6 +18,7 @@ import { transformOutput } from '../../../../detections/containers/detection_eng
import type { TimeframePreviewOptions } from '../../../../detections/pages/detection_engine/rules/types';
import { usePreviewInvocationCount } from './use_preview_invocation_count';
import * as i18n from './translations';
+import { PreviewRuleEventTypes } from '../../../../common/lib/telemetry';
const emptyPreviewRule: RulePreviewResponse = {
previewId: undefined,
@@ -58,7 +59,7 @@ export const usePreviewRule = ({
const createPreviewId = async () => {
if (rule != null) {
try {
- telemetry.reportPreviewRule({
+ telemetry.reportEvent(PreviewRuleEventTypes.PreviewRule, {
loggedRequestsEnabled: enableLoggedRequests ?? false,
ruleType: rule.type,
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx
index cc8f2abda9c4e..9bcf35fdb13ca 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx
@@ -202,7 +202,9 @@ const onOpenTimeline = jest.fn();
const COMBO_BOX_TOGGLE_BUTTON_TEST_ID = 'comboBoxToggleListButton';
const VERSION_INPUT_TEST_ID = 'relatedIntegrationVersionDependency';
-describe('StepDefineRule', () => {
+// Failing: See https://github.com/elastic/kibana/issues/199648
+// Failing: See https://github.com/elastic/kibana/issues/199700
+describe.skip('StepDefineRule', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseRuleFromTimeline.mockReturnValue({ onOpenTimeline, loading: false });
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx
index 4ed02135143ff..9a9cb57713359 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx
@@ -32,7 +32,7 @@ jest.mock('../../../../../common/hooks/use_experimental_features', () => {
});
const mockTelemetry = {
- reportEventLogShowSourceEventDateRange: jest.fn(),
+ reportEvent: jest.fn(),
};
const mockedUseKibana = {
@@ -91,6 +91,6 @@ describe('ExecutionLogTable', () => {
fireEvent.click(switchButton);
- expect(mockTelemetry.reportEventLogShowSourceEventDateRange).toHaveBeenCalled();
+ expect(mockTelemetry.reportEvent).toHaveBeenCalled();
});
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx
index 4546e55522ce5..296323213a6db 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx
@@ -85,6 +85,7 @@ import {
getSourceEventTimeRangeColumns,
} from './execution_log_columns';
import { ExecutionLogSearchBar } from './execution_log_search_bar';
+import { EventLogEventTypes } from '../../../../../common/lib/telemetry';
const EXECUTION_UUID_FIELD_NAME = 'kibana.alert.rule.execution.uuid';
@@ -470,7 +471,7 @@ const ExecutionLogTableComponent: React.FC = ({
(e: EuiSwitchEvent) => {
const isVisible = e.target.checked;
onShowSourceEventTimeRange(isVisible);
- telemetry.reportEventLogShowSourceEventDateRange({
+ telemetry.reportEvent(EventLogEventTypes.EventLogShowSourceEventDateRange, {
isVisible,
});
},
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx
index b2cdd83d6f43f..faf19fd5dd24e 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx
@@ -14,6 +14,7 @@ import { TestProviders } from '../../../../common/mock';
import { useKibana } from '../../../../common/lib/kibana';
import * as i18n from '../../translations';
import type { BackfillRow } from '../../types';
+import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
jest.mock('../../../../common/hooks/use_app_toasts');
jest.mock('../../api/hooks/use_delete_backfill');
@@ -25,7 +26,7 @@ const mockUseKibana = useKibana as jest.Mock;
describe('StopBackfill', () => {
const mockTelemetry = {
- reportManualRuleRunCancelJob: jest.fn(),
+ reportEvent: jest.fn(),
};
const addSuccess = jest.fn();
@@ -90,11 +91,14 @@ describe('StopBackfill', () => {
fireEvent.click(getByTestId('confirmModalConfirmButton'));
await waitFor(() => {
- expect(mockTelemetry.reportManualRuleRunCancelJob).toHaveBeenCalledWith({
- totalTasks: backfill.total,
- completedTasks: backfill.complete,
- errorTasks: backfill.error,
- });
+ expect(mockTelemetry.reportEvent).toHaveBeenCalledWith(
+ ManualRuleRunEventTypes.ManualRuleRunCancelJob,
+ {
+ totalTasks: backfill.total,
+ completedTasks: backfill.complete,
+ errorTasks: backfill.error,
+ }
+ );
});
expect(addSuccess).toHaveBeenCalledWith(i18n.BACKFILLS_TABLE_STOP_CONFIRMATION_SUCCESS);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx
index 84acf0b014d60..51d09dc323d89 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx
@@ -12,6 +12,7 @@ import { useDeleteBackfill } from '../../api/hooks/use_delete_backfill';
import * as i18n from '../../translations';
import type { BackfillRow } from '../../types';
import { useKibana } from '../../../../common/lib/kibana';
+import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
export const StopBackfill = ({ backfill }: { backfill: BackfillRow }) => {
const { telemetry } = useKibana().services;
@@ -19,7 +20,7 @@ export const StopBackfill = ({ backfill }: { backfill: BackfillRow }) => {
const deleteBackfillMutation = useDeleteBackfill({
onSuccess: () => {
closeModal();
- telemetry.reportManualRuleRunCancelJob({
+ telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunCancelJob, {
totalTasks: backfill.total,
completedTasks: backfill.complete,
errorTasks: backfill.error,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx
index 36bdf8a8bf821..ce70bc08bd722 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx
@@ -11,6 +11,7 @@ import { useKibana } from '../../../common/lib/kibana';
import { useKibana as mockUseKibana } from '../../../common/lib/kibana/__mocks__';
import { TestProviders } from '../../../common/mock';
import { useScheduleRuleRun } from './use_schedule_rule_run';
+import { ManualRuleRunEventTypes } from '../../../common/lib/telemetry';
const mockUseScheduleRuleRunMutation = jest.fn();
@@ -28,7 +29,7 @@ const mockedUseKibana = {
services: {
...mockUseKibana().services,
telemetry: {
- reportManualRuleRunExecute: jest.fn(),
+ reportEvent: jest.fn(),
},
},
};
@@ -61,7 +62,7 @@ describe('When using the `useScheduleRuleRun()` hook', () => {
);
});
- it('should call reportManualRuleRunExecute with success status on success', async () => {
+ it('should call reportEvent with success status on success', async () => {
const { result, waitFor } = renderHook(() => useScheduleRuleRun(), {
wrapper: TestProviders,
});
@@ -77,14 +78,17 @@ describe('When using the `useScheduleRuleRun()` hook', () => {
return mockUseScheduleRuleRunMutation.mock.calls.length > 0;
});
- expect(mockedUseKibana.services.telemetry.reportManualRuleRunExecute).toHaveBeenCalledWith({
- rangeInMs: timeRange.endDate.diff(timeRange.startDate),
- status: 'success',
- rulesCount: 1,
- });
+ expect(mockedUseKibana.services.telemetry.reportEvent).toHaveBeenCalledWith(
+ ManualRuleRunEventTypes.ManualRuleRunExecute,
+ {
+ rangeInMs: timeRange.endDate.diff(timeRange.startDate),
+ status: 'success',
+ rulesCount: 1,
+ }
+ );
});
- it('should call reportManualRuleRunExecute with error status on failure', async () => {
+ it('should call reportEvent with error status on failure', async () => {
const { result, waitFor } = renderHook(() => useScheduleRuleRun(), {
wrapper: TestProviders,
});
@@ -100,10 +104,13 @@ describe('When using the `useScheduleRuleRun()` hook', () => {
return mockUseScheduleRuleRunMutation.mock.calls.length > 0;
});
- expect(mockedUseKibana.services.telemetry.reportManualRuleRunExecute).toHaveBeenCalledWith({
- rangeInMs: timeRange.endDate.diff(timeRange.startDate),
- status: 'error',
- rulesCount: 1,
- });
+ expect(mockedUseKibana.services.telemetry.reportEvent).toHaveBeenCalledWith(
+ ManualRuleRunEventTypes.ManualRuleRunExecute,
+ {
+ rangeInMs: timeRange.endDate.diff(timeRange.startDate),
+ status: 'error',
+ rulesCount: 1,
+ }
+ );
});
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts
index 7599d8685d3c0..94f85a30b3ceb 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts
@@ -12,6 +12,7 @@ import { useScheduleRuleRunMutation } from '../api/hooks/use_schedule_rule_run_m
import type { ScheduleBackfillProps } from '../types';
import * as i18n from '../translations';
+import { ManualRuleRunEventTypes } from '../../../common/lib/telemetry';
export function useScheduleRuleRun() {
const { mutateAsync } = useScheduleRuleRunMutation();
@@ -22,7 +23,7 @@ export function useScheduleRuleRun() {
async (options: ScheduleBackfillProps) => {
try {
const results = await mutateAsync(options);
- telemetry.reportManualRuleRunExecute({
+ telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunExecute, {
rangeInMs: options.timeRange.endDate.diff(options.timeRange.startDate),
status: 'success',
rulesCount: options.ruleIds.length,
@@ -31,7 +32,7 @@ export function useScheduleRuleRun() {
return results;
} catch (error) {
addError(error, { title: i18n.BACKFILL_SCHEDULE_ERROR_TITLE });
- telemetry.reportManualRuleRunExecute({
+ telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunExecute, {
rangeInMs: options.timeRange.endDate.diff(options.timeRange.startDate),
status: 'error',
rulesCount: options.ruleIds.length,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx
index 68e58b4db073f..22f10605b3685 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx
@@ -45,6 +45,7 @@ import type { ExecuteBulkActionsDryRun } from './use_bulk_actions_dry_run';
import { computeDryRunEditPayload } from './utils/compute_dry_run_edit_payload';
import { transformExportDetailsToDryRunResult } from './utils/dry_run_result';
import { prepareSearchParams } from './utils/prepare_search_params';
+import { ManualRuleRunEventTypes } from '../../../../../common/lib/telemetry';
interface UseBulkActionsArgs {
filterOptions: FilterOptions;
@@ -234,7 +235,7 @@ export const useBulkActions = ({
}
const modalManualRuleRunConfirmationResult = await showManualRuleRunConfirmation();
- startServices.telemetry.reportManualRuleRunOpenModal({
+ startServices.telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunOpenModal, {
type: 'bulk',
});
if (modalManualRuleRunConfirmationResult === null) {
@@ -252,7 +253,7 @@ export const useBulkActions = ({
},
});
- startServices.telemetry.reportManualRuleRunExecute({
+ startServices.telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunExecute, {
rangeInMs: modalManualRuleRunConfirmationResult.endDate.diff(
modalManualRuleRunConfirmationResult.startDate
),
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx
index 4cc7a03426657..f3d0930d7c1fe 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx
@@ -25,6 +25,7 @@ import { useDownloadExportedRules } from '../../../rule_management/logic/bulk_ac
import { useHasActionsPrivileges } from './use_has_actions_privileges';
import type { TimeRange } from '../../../rule_gaps/types';
import { useScheduleRuleRun } from '../../../rule_gaps/logic/use_schedule_rule_run';
+import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
export const useRulesTableActions = ({
showExceptionsDuplicateConfirmation,
@@ -126,7 +127,7 @@ export const useRulesTableActions = ({
onClick: async (rule: Rule) => {
startTransaction({ name: SINGLE_RULE_ACTIONS.MANUAL_RULE_RUN });
const modalManualRuleRunConfirmationResult = await showManualRuleRunConfirmation();
- telemetry.reportManualRuleRunOpenModal({
+ telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunOpenModal, {
type: 'single',
});
if (modalManualRuleRunConfirmationResult === null) {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx
index 50c35e7a6e529..a11246b4f52ba 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx
@@ -10,11 +10,12 @@ import { render, screen, fireEvent } from '@testing-library/react';
import { ExecutionRunTypeFilter } from '.';
import { RuleRunTypeEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring';
import { useKibana } from '../../../../../../common/lib/kibana';
+import { EventLogEventTypes } from '../../../../../../common/lib/telemetry';
jest.mock('../../../../../../common/lib/kibana');
const mockTelemetry = {
- reportEventLogFilterByRunType: jest.fn(),
+ reportEvent: jest.fn(),
};
const mockUseKibana = useKibana as jest.Mock;
@@ -28,7 +29,7 @@ mockUseKibana.mockReturnValue({
const items = [RuleRunTypeEnum.backfill, RuleRunTypeEnum.standard];
describe('ExecutionRunTypeFilter', () => {
- it('calls telemetry.reportEventLogFilterByRunType on selection change', () => {
+ it('calls telemetry.reportEvent on selection change', () => {
const handleChange = jest.fn();
render();
@@ -40,8 +41,11 @@ describe('ExecutionRunTypeFilter', () => {
fireEvent.click(manualRun);
expect(handleChange).toHaveBeenCalledWith([RuleRunTypeEnum.backfill]);
- expect(mockTelemetry.reportEventLogFilterByRunType).toHaveBeenCalledWith({
- runType: [RuleRunTypeEnum.backfill],
- });
+ expect(mockTelemetry.reportEvent).toHaveBeenCalledWith(
+ EventLogEventTypes.EventLogFilterByRunType,
+ {
+ runType: [RuleRunTypeEnum.backfill],
+ }
+ );
});
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx
index 9f144410a7590..4e1c44517c058 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx
@@ -15,6 +15,7 @@ import {
RULE_EXECUTION_TYPE_STANDARD,
} from '../../../../../../common/translations';
import { useKibana } from '../../../../../../common/lib/kibana';
+import { EventLogEventTypes } from '../../../../../../common/lib/telemetry';
interface ExecutionRunTypeFilterProps {
items: RuleRunType[];
@@ -42,7 +43,9 @@ const ExecutionRunTypeFilterComponent: React.FC = (
const handleSelectionChange = useCallback(
(types: RuleRunType[]) => {
onChange(types);
- telemetry.reportEventLogFilterByRunType({ runType: types });
+ telemetry.reportEvent(EventLogEventTypes.EventLogFilterByRunType, {
+ runType: types,
+ });
},
[onChange, telemetry]
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx
index cf57c9d59b080..5392d730c9e0d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx
@@ -20,6 +20,7 @@ import { useKibana as mockUseKibana } from '../../../common/lib/kibana/__mocks__
import { createTelemetryServiceMock } from '../../../common/lib/telemetry/telemetry_service.mock';
import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query';
import { getQuery, groupingSearchResponse } from './grouping_settings/mock';
+import { AlertsEventTypes } from '../../../common/lib/telemetry';
jest.mock('../../containers/detection_engine/alerts/use_query');
jest.mock('../../../sourcerer/containers');
@@ -553,17 +554,23 @@ describe('GroupedAlertsTable', () => {
fireEvent.click(getByTestId('group-selector-dropdown'));
fireEvent.click(getByTestId('panel-user.name'));
- expect(mockedTelemetry.reportAlertsGroupingChanged).toHaveBeenCalledWith({
- groupByField: 'user.name',
- tableId: testProps.tableId,
- });
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(
+ AlertsEventTypes.AlertsGroupingChanged,
+ {
+ groupByField: 'user.name',
+ tableId: testProps.tableId,
+ }
+ );
fireEvent.click(getByTestId('group-selector-dropdown'));
fireEvent.click(getByTestId('panel-host.name'));
- expect(mockedTelemetry.reportAlertsGroupingChanged).toHaveBeenCalledWith({
- groupByField: 'host.name',
- tableId: testProps.tableId,
- });
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledWith(
+ AlertsEventTypes.AlertsGroupingChanged,
+ {
+ groupByField: 'host.name',
+ tableId: testProps.tableId,
+ }
+ );
});
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
index a1cbdc8004727..c4dd142fc71b3 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
@@ -25,7 +25,7 @@ import type { RunTimeMappings } from '../../../sourcerer/store/model';
import { renderGroupPanel, getStats } from './grouping_settings';
import { useKibana } from '../../../common/lib/kibana';
import { GroupedSubLevel } from './alerts_sub_grouping';
-import { track } from '../../../common/lib/telemetry';
+import { AlertsEventTypes, track } from '../../../common/lib/telemetry';
export interface AlertsTableComponentProps {
currentAlertStatusFilterValue?: Status[];
@@ -80,14 +80,18 @@ const GroupedAlertsTableComponent: React.FC = (props)
const { onGroupChange, onGroupToggle } = useMemo(
() => ({
onGroupChange: ({ groupByField, tableId }: { groupByField: string; tableId: string }) => {
- telemetry.reportAlertsGroupingChanged({ groupByField, tableId });
+ telemetry.reportEvent(AlertsEventTypes.AlertsGroupingChanged, { groupByField, tableId });
},
onGroupToggle: (param: {
isOpen: boolean;
groupName?: string | undefined;
groupNumber: number;
groupingId: string;
- }) => telemetry.reportAlertsGroupingToggled({ ...param, tableId: param.groupingId }),
+ }) =>
+ telemetry.reportEvent(AlertsEventTypes.AlertsGroupingToggled, {
+ ...param,
+ tableId: param.groupingId,
+ }),
}),
[telemetry]
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx
index 6a7b11ee0e192..e5e753b1c7763 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx
@@ -28,7 +28,7 @@ import {
import { FILTER_ACKNOWLEDGED, FILTER_CLOSED, FILTER_OPEN } from '../../../../../common/types';
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
import * as i18n from '../translations';
-import { getTelemetryEvent, METRIC_TYPE, track } from '../../../../common/lib/telemetry';
+import { AlertsEventTypes, METRIC_TYPE, track } from '../../../../common/lib/telemetry';
import type { StartServices } from '../../../../types';
export interface TakeActionsProps {
@@ -36,6 +36,18 @@ export interface TakeActionsProps {
showAlertStatusActions?: boolean;
}
+const getTelemetryEvent = {
+ groupedAlertsTakeAction: ({
+ tableId,
+ groupNumber,
+ status,
+ }: {
+ tableId: string;
+ groupNumber: number;
+ status: AlertWorkflowStatus;
+ }) => `alerts_table_${tableId}_group-${groupNumber}_mark-${status}`,
+};
+
export const useGroupTakeActionsItems = ({
currentStatus,
showAlertStatusActions = true,
@@ -58,7 +70,7 @@ export const useGroupTakeActionsItems = ({
status: 'open' | 'closed' | 'acknowledged';
groupByField: string;
}) => {
- telemetry.reportAlertsGroupingTakeAction(params);
+ telemetry.reportEvent(AlertsEventTypes.AlertsGroupingTakeAction, params);
},
[telemetry]
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx
index e1ff950bc5e32..408a13fd1a9cf 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx
@@ -16,6 +16,7 @@ import { RuleActionsOverflow } from '.';
import { mockRule } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock';
import { TestProviders } from '../../../../common/mock';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
+import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
const showBulkDuplicateExceptionsConfirmation = () => Promise.resolve(null);
const showManualRuleRunConfirmation = () => Promise.resolve(null);
@@ -28,7 +29,7 @@ jest.mock('../../../../detection_engine/rule_management/logic/bulk_actions/use_b
jest.mock('../../../../detection_engine/rule_gaps/logic/use_schedule_rule_run');
jest.mock('../../../../common/lib/apm/use_start_transaction');
jest.mock('../../../../common/hooks/use_app_toasts');
-const mockReportManualRuleRunOpenModal = jest.fn();
+const mockReportEvent = jest.fn();
jest.mock('../../../../common/lib/kibana', () => {
const actual = jest.requireActual('../../../../common/lib/kibana');
return {
@@ -36,8 +37,8 @@ jest.mock('../../../../common/lib/kibana', () => {
useKibana: jest.fn().mockReturnValue({
services: {
telemetry: {
- reportManualRuleRunOpenModal: (params: { type: 'single' | 'bulk' }) =>
- mockReportManualRuleRunOpenModal(params),
+ reportEvent: (eventType: ManualRuleRunEventTypes, params: { type: 'single' | 'bulk' }) =>
+ mockReportEvent(eventType, params),
},
application: {
navigateToApp: jest.fn(),
@@ -274,7 +275,7 @@ describe('RuleActionsOverflow', () => {
expect(getByTestId('rules-details-popover')).not.toHaveTextContent(/.+/);
});
- test('it calls telemetry.reportManualRuleRunOpenModal when rules-details-manual-rule-run is clicked', async () => {
+ test('it calls telemetry.reportEvent when rules-details-manual-rule-run is clicked', async () => {
const { getByTestId } = render(
{
fireEvent.click(getByTestId('rules-details-manual-rule-run'));
await waitFor(() => {
- expect(mockReportManualRuleRunOpenModal).toHaveBeenCalledWith({
- type: 'single',
- });
+ expect(mockReportEvent).toHaveBeenCalledWith(
+ ManualRuleRunEventTypes.ManualRuleRunOpenModal,
+ {
+ type: 'single',
+ }
+ );
});
});
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx
index 68defd759938f..a786b95979d43 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx
@@ -34,6 +34,7 @@ import {
import { useDownloadExportedRules } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules';
import * as i18nActions from '../../../pages/detection_engine/rules/translations';
import * as i18n from './translations';
+import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
const MyEuiButtonIcon = styled(EuiButtonIcon)`
&.euiButtonIcon {
@@ -161,7 +162,7 @@ const RuleActionsOverflowComponent = ({
startTransaction({ name: SINGLE_RULE_ACTIONS.MANUAL_RULE_RUN });
closePopover();
const modalManualRuleRunConfirmationResult = await showManualRuleRunConfirmation();
- telemetry.reportManualRuleRunOpenModal({
+ telemetry.reportEvent(ManualRuleRunEventTypes.ManualRuleRunOpenModal, {
type: 'single',
});
if (modalManualRuleRunConfirmationResult === null) {
diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx
index dbd47281c5f2a..484162cbf7d42 100644
--- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx
+++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx
@@ -20,7 +20,7 @@ import { useSourcererDataView } from '../../../sourcerer/containers';
import { SourcererScopeName } from '../../../sourcerer/store/model';
import { updateGroups } from '../../../common/store/grouping/actions';
import { useKibana } from '../../../common/lib/kibana';
-import { METRIC_TYPE, track } from '../../../common/lib/telemetry';
+import { METRIC_TYPE, AlertsEventTypes, track } from '../../../common/lib/telemetry';
import { useDataTableFilters } from '../../../common/hooks/use_data_table_filters';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
import { RightTopMenu } from '../../../common/components/events_viewer/right_top_menu';
@@ -47,7 +47,10 @@ export const getPersistentControlsHook = (tableId: TableId) => {
METRIC_TYPE.CLICK,
getTelemetryEvent.groupChanged({ groupingId: tableId, selected: groupSelection })
);
- telemetry.reportAlertsGroupingChanged({ groupByField: groupSelection, tableId });
+ telemetry.reportEvent(AlertsEventTypes.AlertsGroupingChanged, {
+ groupByField: groupSelection,
+ tableId,
+ });
},
[telemetry]
);
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/asset_criticality_file_uploader.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/asset_criticality_file_uploader.tsx
index 0acd1f831ca71..9c76c1e5f5082 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/asset_criticality_file_uploader.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/asset_criticality_file_uploader.tsx
@@ -16,6 +16,7 @@ import { AssetCriticalityResultStep } from './components/result_step';
import { useEntityAnalyticsRoutes } from '../../api/api';
import { useFileValidation, useNavigationSteps } from './hooks';
import type { OnCompleteParams } from './types';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export const AssetCriticalityFileUploader: React.FC = () => {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
@@ -24,7 +25,7 @@ export const AssetCriticalityFileUploader: React.FC = () => {
const onValidationComplete = useCallback(
({ validatedFile, processingStartTime, processingEndTime, tookMs }: OnCompleteParams) => {
- telemetry.reportAssetCriticalityCsvPreviewGenerated({
+ telemetry.reportEvent(EntityEventTypes.AssetCriticalityCsvPreviewGenerated, {
file: {
size: validatedFile.size,
},
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.test.tsx
index ca1dcfb6e7f42..f3200b5c6ee44 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.test.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.test.tsx
@@ -20,7 +20,7 @@ jest.mock('../../../../common/lib/kibana/kibana_react', () => ({
useKibana: () => ({
services: {
telemetry: {
- reportAssetCriticalityCsvImported: jest.fn(),
+ reportEvent: jest.fn(),
},
},
}),
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.tsx
index 538e19f4d5fd6..c4dadc756c15b 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/validation_step.tsx
@@ -23,6 +23,7 @@ import { downloadBlob } from '../../../../common/utils/download_blob';
import { useKibana } from '../../../../common/lib/kibana/kibana_react';
import type { ValidatedFile } from '../types';
import { buildAnnotationsFromError } from '../helpers';
+import { EntityEventTypes } from '../../../../common/lib/telemetry';
export interface AssetCriticalityValidationStepProps {
validatedFile: ValidatedFile;
@@ -42,7 +43,7 @@ export const AssetCriticalityValidationStep: React.FC {
- telemetry.reportAssetCriticalityCsvImported({
+ telemetry.reportEvent(EntityEventTypes.AssetCriticalityCsvImported, {
file: {
size: fileSize,
},
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/hooks.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/hooks.ts
index 107ba6348ac70..0472f5002fcb1 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/hooks.ts
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/hooks.ts
@@ -17,6 +17,7 @@ import { useKibana } from '../../../common/lib/kibana';
import type { OnCompleteParams } from './types';
import type { ReducerState } from './reducer';
import { getStepStatus, isValidationStep } from './helpers';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
interface UseFileChangeCbParams {
onError: (errorMessage: string, file: File) => void;
@@ -35,7 +36,7 @@ export const useFileValidation = ({ onError, onComplete }: UseFileChangeCbParams
},
file: File
) => {
- telemetry.reportAssetCriticalityFileSelected({
+ telemetry.reportEvent(EntityEventTypes.AssetCriticalityFileSelected, {
valid: false,
errorCode: error.code,
file: {
@@ -62,7 +63,7 @@ export const useFileValidation = ({ onError, onComplete }: UseFileChangeCbParams
return;
}
- telemetry.reportAssetCriticalityFileSelected({
+ telemetry.reportEvent(EntityEventTypes.AssetCriticalityFileSelected, {
valid: true,
file: {
size: file.size,
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.test.tsx
index 8400578b85c4f..32234547ca628 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.test.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.test.tsx
@@ -10,6 +10,7 @@ import { AnomalyEntity } from '../../../common/components/ml/anomaly/use_anomali
import { createTelemetryServiceMock } from '../../../common/lib/telemetry/telemetry_service.mock';
import { TestProviders } from '../../../common/mock';
import { AnomaliesCountLink } from './anomalies_count_link';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
const mockedTelemetry = createTelemetryServiceMock();
jest.mock('../../../common/lib/kibana', () => {
@@ -37,6 +38,9 @@ describe('AnomaliesCountLink', () => {
fireEvent.click(getByRole('button'));
- expect(mockedTelemetry.reportAnomaliesCountClicked).toHaveBeenLastCalledWith({ jobId, count });
+ expect(mockedTelemetry.reportEvent).toHaveBeenLastCalledWith(
+ EntityEventTypes.AnomaliesCountClicked,
+ { jobId, count }
+ );
});
});
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.tsx
index bb32564acb1b9..6068d0ece6676 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_anomalies/anomalies_count_link.tsx
@@ -15,6 +15,7 @@ import { HostsType } from '../../../explore/hosts/store/model';
import { UsersType } from '../../../explore/users/store/model';
import { useKibana } from '../../../common/lib/kibana';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export const AnomaliesCountLink = ({
count,
@@ -36,7 +37,7 @@ export const AnomaliesCountLink = ({
const onClick = useCallback(() => {
if (!jobId) return;
- telemetry.reportAnomaliesCountClicked({
+ telemetry.reportEvent(EntityEventTypes.AnomaliesCountClicked, {
jobId,
count,
});
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_risk_score/index.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_risk_score/index.tsx
index ffa1afffdf21b..08ce79cc74c17 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_risk_score/index.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_analytics_risk_score/index.tsx
@@ -38,6 +38,7 @@ import { useRiskScore } from '../../api/hooks/use_risk_score';
import { UserPanelKey } from '../../../flyout/entity_details/user_right';
import { RiskEnginePrivilegesCallOut } from '../risk_engine_privileges_callout';
import { useMissingRiskEnginePrivileges } from '../../hooks/use_missing_risk_engine_privileges';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export const ENTITY_RISK_SCORE_TABLE_ID = 'entity-risk-score-table';
@@ -51,7 +52,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
const openEntityOnAlertsPage = useCallback(
(entityName: string) => {
- telemetry.reportEntityAlertsClicked({ entity: riskEntity });
+ telemetry.reportEvent(EntityEventTypes.EntityAlertsClicked, { entity: riskEntity });
openAlertsPageWithFilters([
{
title: getRiskEntityTranslation(riskEntity),
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/hooks/use_risk_input_actions.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/hooks/use_risk_input_actions.ts
index b12f82128c824..a8a7f60e075ae 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/hooks/use_risk_input_actions.ts
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/hooks/use_risk_input_actions.ts
@@ -17,6 +17,7 @@ import { SourcererScopeName } from '../../../../sourcerer/store/model';
import { useAddBulkToTimelineAction } from '../../../../detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline';
import { useKibana } from '../../../../common/lib/kibana/kibana_react';
import type { InputAlert } from '../../../hooks/use_risk_contributing_alerts';
+import { EntityEventTypes } from '../../../../common/lib/telemetry';
/**
* The returned actions only support alerts risk inputs.
@@ -61,7 +62,7 @@ export const useRiskInputActions = (inputs: InputAlert[], closePopover: () => vo
},
addToNewTimeline: () => {
- telemetry.reportAddRiskInputToTimelineClicked({
+ telemetry.reportEvent(EntityEventTypes.AddRiskInputToTimelineClicked, {
quantity: inputs.length,
});
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts
index 21e73241451e5..8aefbe2b44af1 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts
@@ -17,6 +17,7 @@ import type {
} from '../../../../../common/api/entity_analytics';
import { useEntityStoreRoutes } from '../../../api/entity_store';
import { ENTITY_STORE_ENGINE_STATUS, useEntityEngineStatus } from './use_entity_engine_status';
+import { EntityEventTypes } from '../../../../common/lib/telemetry';
const ENTITY_STORE_ENABLEMENT_INIT = 'ENTITY_STORE_ENABLEMENT_INIT';
@@ -49,7 +50,7 @@ export const useEntityStoreEnablement = () => {
});
const enable = useCallback(() => {
- telemetry?.reportEntityStoreInit({
+ telemetry?.reportEvent(EntityEventTypes.EntityStoreDashboardInitButtonClicked, {
timestamp: new Date().toISOString(),
});
return initialize().then(() => setPolling(true));
@@ -76,7 +77,7 @@ export const useInitEntityEngineMutation = (options?: UseMutationOptions<{}>) =>
const { initEntityStore } = useEntityStoreRoutes();
return useMutation(
() => {
- telemetry?.reportEntityStoreEnablement({
+ telemetry?.reportEvent(EntityEventTypes.EntityStoreEnablementToggleClicked, {
timestamp: new Date().toISOString(),
action: 'start',
});
@@ -106,7 +107,7 @@ export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) =>
const { stopEntityStore } = useEntityStoreRoutes();
return useMutation(
() => {
- telemetry?.reportEntityStoreEnablement({
+ telemetry?.reportEvent(EntityEventTypes.EntityStoreEnablementToggleClicked, {
timestamp: new Date().toISOString(),
action: 'stop',
});
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx
index 2dec7d07ce6e8..0c42543a7f91e 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx
@@ -43,6 +43,7 @@ import {
LENS_VISUALIZATION_MIN_WIDTH,
SUMMARY_TABLE_MIN_WIDTH,
} from './common';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface RiskSummaryProps {
riskScoreData: RiskScoreState;
@@ -84,7 +85,7 @@ const FlyoutRiskSummaryComponent = ({
(isOpen: boolean) => {
const entity = isUserRiskData(riskData) ? 'user' : 'host';
- telemetry.reportToggleRiskSummaryClicked({
+ telemetry.reportEvent(EntityEventTypes.ToggleRiskSummaryClicked, {
entity,
action: isOpen ? 'show' : 'hide',
});
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.test.tsx
index 8adbc2c7578df..c50d5a1be7747 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.test.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.test.tsx
@@ -24,7 +24,7 @@ jest.mock('../../../common/lib/kibana', () => {
describe('SeverityFilter', () => {
beforeEach(() => {
- mockedTelemetry.reportEntityRiskFiltered.mockClear();
+ mockedTelemetry.reportEvent.mockClear();
});
it('sends telemetry when selecting a classification', () => {
@@ -38,7 +38,7 @@ describe('SeverityFilter', () => {
fireEvent.click(getByTestId('risk-filter-item-Unknown'));
- expect(mockedTelemetry.reportEntityRiskFiltered).toHaveBeenCalledTimes(1);
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledTimes(1);
});
it('does not send telemetry when deselecting a classification', () => {
@@ -61,6 +61,6 @@ describe('SeverityFilter', () => {
fireEvent.click(getByTestId('risk-filter-popoverButton'));
fireEvent.click(getByTestId('risk-filter-item-Unknown'));
- expect(mockedTelemetry.reportEntityRiskFiltered).toHaveBeenCalledTimes(0);
+ expect(mockedTelemetry.reportEvent).toHaveBeenCalledTimes(0);
});
});
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.tsx
index 6aa150e40afae..6da522658894d 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.tsx
+++ b/x-pack/plugins/security_solution/public/entity_analytics/components/severity/severity_filter.tsx
@@ -13,6 +13,7 @@ import type { RiskScoreEntity, RiskSeverity } from '../../../../common/search_st
import { RiskScoreLevel } from './common';
import { ENTITY_RISK_LEVEL } from '../risk_score/translations';
import { useKibana } from '../../../common/lib/kibana';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface SeverityFilterProps {
riskEntity?: RiskScoreEntity;
@@ -35,7 +36,7 @@ export const SeverityFilter: React.FC = ({
>(
(newSelection, changedSeverity, changedStatus) => {
if (changedStatus === 'on') {
- telemetry.reportEntityRiskFiltered({
+ telemetry.reportEvent(EntityEventTypes.EntityRiskFiltered, {
entity: riskEntity,
selectedSeverity: changedSeverity,
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx
index c315e991d9f06..e3fd2fef8bc6e 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx
@@ -71,6 +71,7 @@ import type { NarrowDateRange } from '../../../../common/components/ml/types';
import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight';
import { VulnerabilitiesInsight } from '../../shared/components/vulnerabilities_insight';
import { AlertCountInsight } from '../../shared/components/alert_count_insight';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
const HOST_DETAILS_ID = 'entities-hosts-details';
const RELATED_USERS_ID = 'entities-hosts-related-users';
@@ -134,7 +135,7 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s
banner: HOST_PREVIEW_BANNER,
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx
index 38bf50a679ee2..3b45cd71b0a6f 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx
@@ -28,6 +28,7 @@ import { ALERT_PREVIEW_BANNER } from '../../preview/constants';
import { useLicense } from '../../../../common/hooks/use_license';
import { useSessionPreview } from '../../right/hooks/use_session_preview';
import { SessionViewNoDataMessage } from '../../shared/components/session_view_no_data_message';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
export const SESSION_VIEW_ID = 'session-view';
@@ -74,7 +75,7 @@ export const SessionView: FC = () => {
isPreviewMode: true,
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx
index 2f98c641b5954..e88cbb54f9471 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx
@@ -69,6 +69,7 @@ import { PreviewLink } from '../../../shared/components/preview_link';
import type { NarrowDateRange } from '../../../../common/components/ml/types';
import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight';
import { AlertCountInsight } from '../../shared/components/alert_count_insight';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
const USER_DETAILS_ID = 'entities-users-details';
const RELATED_HOSTS_ID = 'entities-users-related-hosts';
@@ -133,7 +134,7 @@ export const UserDetails: React.FC = ({ userName, timestamp, s
banner: USER_PREVIEW_BANNER,
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx
index 56375426c5f68..6dcf9da06d2b6 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx
@@ -22,6 +22,7 @@ import { getField } from '../shared/utils';
import { EventKind } from '../shared/constants/event_kinds';
import { useDocumentDetailsContext } from '../shared/context';
import type { DocumentDetailsProps } from '../shared/types';
+import { DocumentEventTypes } from '../../../common/lib/telemetry/types';
export type LeftPanelPaths = 'visualize' | 'insights' | 'investigation' | 'response' | 'notes';
export const LeftPanelVisualizeTab: LeftPanelPaths = 'visualize';
@@ -75,7 +76,7 @@ export const LeftPanel: FC> = memo(({ path }) => {
scopeId,
},
});
- telemetry.reportDetailsFlyoutTabClicked({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutTabClicked, {
location: scopeId,
panel: 'left',
tabId,
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx
index 3917de03e2e34..0982e10485ba3 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx
@@ -31,6 +31,7 @@ import { PREVALENCE_TAB_ID, PrevalenceDetails } from '../components/prevalence_d
import { CORRELATIONS_TAB_ID, CorrelationsDetails } from '../components/correlations_details';
import { getField } from '../../shared/utils';
import { EventKind } from '../../shared/constants/event_kinds';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
const ENTITIES_TAB_ID = 'entity';
@@ -113,7 +114,7 @@ export const InsightsTab = memo(() => {
scopeId,
},
});
- telemetry.reportDetailsFlyoutTabClicked({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutTabClicked, {
location: scopeId,
panel: 'left',
tabId: optionId,
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx
index 0201332888675..b2df6c096e279 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx
@@ -16,6 +16,7 @@ import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys';
import { useDocumentDetailsContext } from '../shared/context';
import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids';
import { useKibana } from '../../../common/lib/kibana';
+import { DocumentEventTypes } from '../../../common/lib/telemetry';
/**
* Footer at the bottom of preview panel with a link to open document details flyout
@@ -41,7 +42,7 @@ export const PreviewPanelFooter = () => {
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'right',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx
index 2d2dfddbbbabe..91d5059e5d60a 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx
@@ -22,6 +22,7 @@ import {
RULE_SUMMARY_BUTTON_TEST_ID,
} from './test_ids';
import { RULE_PREVIEW_BANNER, RulePreviewPanelKey } from '../../../rule_details/right';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
/**
* Displays the rule description of a signal document.
@@ -42,7 +43,7 @@ export const AlertDescription: FC = () => {
isPreviewMode: true,
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx
index be65593364593..af9e8dca1f24f 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx
@@ -32,7 +32,6 @@ export const GraphPreviewContainer: React.FC = () => {
const graphFetchQuery = useFetchGraphData({
req: {
query: {
- actorIds: [],
eventIds,
start: DEFAULT_FROM,
end: DEFAULT_TO,
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx
index c4b0e6e26a820..fc6db84ad4393 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx
@@ -22,6 +22,7 @@ import {
} from './test_ids';
import { useBasicDataFromDetailsData } from '../../shared/hooks/use_basic_data_from_details_data';
import { useDocumentDetailsContext } from '../../shared/context';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
export const ALERT_REASON_BANNER = {
title: i18n.translate(
@@ -55,7 +56,7 @@ export const Reason: FC = () => {
banner: ALERT_REASON_BANNER,
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.test.tsx
new file mode 100644
index 0000000000000..c22ec0caa82c5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.test.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { renderHook } from '@testing-library/react-hooks';
+import { useFetchGraphData } from './use_fetch_graph_data';
+
+const mockUseQuery = jest.fn();
+
+jest.mock('@tanstack/react-query', () => {
+ return {
+ useQuery: (...args: unknown[]) => mockUseQuery(...args),
+ };
+});
+
+describe('useFetchGraphData', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('Should pass default options when options are not provided', () => {
+ renderHook(() => {
+ return useFetchGraphData({
+ req: {
+ query: {
+ eventIds: [],
+ start: '2021-09-01T00:00:00.000Z',
+ end: '2021-09-01T23:59:59.999Z',
+ },
+ },
+ });
+ });
+
+ expect(mockUseQuery.mock.calls).toHaveLength(1);
+ expect(mockUseQuery.mock.calls[0][2]).toEqual({
+ enabled: true,
+ refetchOnWindowFocus: true,
+ });
+ });
+
+ it('Should should not be enabled when enabled set to false', () => {
+ renderHook(() => {
+ return useFetchGraphData({
+ req: {
+ query: {
+ eventIds: [],
+ start: '2021-09-01T00:00:00.000Z',
+ end: '2021-09-01T23:59:59.999Z',
+ },
+ },
+ options: {
+ enabled: false,
+ },
+ });
+ });
+
+ expect(mockUseQuery.mock.calls).toHaveLength(1);
+ expect(mockUseQuery.mock.calls[0][2]).toEqual({
+ enabled: false,
+ refetchOnWindowFocus: true,
+ });
+ });
+
+ it('Should should not be refetchOnWindowFocus when refetchOnWindowFocus set to false', () => {
+ renderHook(() => {
+ return useFetchGraphData({
+ req: {
+ query: {
+ eventIds: [],
+ start: '2021-09-01T00:00:00.000Z',
+ end: '2021-09-01T23:59:59.999Z',
+ },
+ },
+ options: {
+ refetchOnWindowFocus: false,
+ },
+ });
+ });
+
+ expect(mockUseQuery.mock.calls).toHaveLength(1);
+ expect(mockUseQuery.mock.calls[0][2]).toEqual({
+ enabled: true,
+ refetchOnWindowFocus: false,
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.ts
index 2304cfb8d4fd2..9a0e270a9b2e0 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.ts
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_graph_data.ts
@@ -10,6 +10,7 @@ import type {
GraphRequest,
GraphResponse,
} from '@kbn/cloud-security-posture-common/types/graph/latest';
+import { useMemo } from 'react';
import { EVENT_GRAPH_VISUALIZATION_API } from '../../../../../common/constants';
import { useHttp } from '../../../../common/lib/kibana';
@@ -30,6 +31,11 @@ export interface UseFetchGraphDataParams {
* Defaults to true.
*/
enabled?: boolean;
+ /**
+ * If true, the query will refetch on window focus.
+ * Defaults to true.
+ */
+ refetchOnWindowFocus?: boolean;
};
}
@@ -61,18 +67,25 @@ export const useFetchGraphData = ({
req,
options,
}: UseFetchGraphDataParams): UseFetchGraphDataResult => {
- const { actorIds, eventIds, start, end } = req.query;
+ const { eventIds, start, end, esQuery } = req.query;
const http = useHttp();
+ const QUERY_KEY = useMemo(
+ () => ['useFetchGraphData', eventIds, start, end, esQuery],
+ [end, esQuery, eventIds, start]
+ );
const { isLoading, isError, data } = useQuery(
- ['useFetchGraphData', actorIds, eventIds, start, end],
+ QUERY_KEY,
() => {
return http.post(EVENT_GRAPH_VISUALIZATION_API, {
version: '1',
body: JSON.stringify(req),
});
},
- options
+ {
+ enabled: options?.enabled ?? true,
+ refetchOnWindowFocus: options?.refetchOnWindowFocus ?? true,
+ }
);
return {
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx
index 56c24d9562091..9d3262ce1ff3b 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx
@@ -20,6 +20,7 @@ import { PanelContent } from './content';
import type { RightPanelTabType } from './tabs';
import { PanelFooter } from './footer';
import { useFlyoutIsExpandable } from './hooks/use_flyout_is_expandable';
+import { DocumentEventTypes } from '../../../common/lib/telemetry';
export type RightPanelPaths = 'overview' | 'table' | 'json';
@@ -53,7 +54,7 @@ export const RightPanel: FC> = memo(({ path }) =>
// saving which tab is currently selected in the right panel in local storage
storage.set(FLYOUT_STORAGE_KEYS.RIGHT_PANEL_SELECTED_TABS, tabId);
- telemetry.reportDetailsFlyoutTabClicked({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutTabClicked, {
location: scopeId,
panel: 'right',
tabId,
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx
index b4f12fbabf94f..c3ee6a7d7a51a 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx
@@ -13,6 +13,7 @@ import { HeaderActions } from './components/header_actions';
import { FlyoutNavigation } from '../../shared/components/flyout_navigation';
import { DocumentDetailsLeftPanelKey } from '../shared/constants/panel_keys';
import { useDocumentDetailsContext } from '../shared/context';
+import { DocumentEventTypes } from '../../../common/lib/telemetry';
interface PanelNavigationProps {
/**
@@ -35,7 +36,7 @@ export const PanelNavigation: FC = memo(({ flyoutIsExpanda
scopeId,
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.tsx
index 516a43332d29f..a4539ed7e6415 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.tsx
@@ -19,6 +19,7 @@ import {
} from '../constants/panel_keys';
import { Flyouts } from '../constants/flyouts';
import { isTimelineScope } from '../../../../helpers';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
export interface UseNavigateToAnalyzerParams {
/**
@@ -107,7 +108,7 @@ export const useNavigateToAnalyzer = ({
if (isFlyoutOpen) {
openLeftPanel(left);
openPreviewPanel(preview);
- telemetry.reportDetailsFlyoutTabClicked({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutTabClicked, {
location: scopeId,
panel: 'left',
tabId: 'visualize',
@@ -118,7 +119,7 @@ export const useNavigateToAnalyzer = ({
left,
preview,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.tsx
index b8234321217e6..f0b2733998c97 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.tsx
@@ -12,6 +12,7 @@ import type { Maybe } from '@kbn/timelines-plugin/common/search_strategy/common'
import { useKibana } from '../../../../common/lib/kibana';
import { SESSION_VIEW_ID } from '../../left/components/session_view';
import { DocumentDetailsLeftPanelKey, DocumentDetailsRightPanelKey } from '../constants/panel_keys';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
export interface UseNavigateToSessionViewParams {
/**
@@ -83,7 +84,7 @@ export const useNavigateToSessionView = ({
const navigateToSessionView = useCallback(() => {
if (isFlyoutOpen) {
openLeftPanel(left);
- telemetry.reportDetailsFlyoutTabClicked({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutTabClicked, {
location: scopeId,
panel: 'left',
tabId: 'visualize',
@@ -93,7 +94,7 @@ export const useNavigateToSessionView = ({
right,
left,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx
index adc54b58f75cb..83fa75474a1cc 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx
@@ -35,6 +35,7 @@ import { useObservedHost } from './hooks/use_observed_host';
import { HostDetailsPanelKey } from '../host_details_left';
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
import { HostPreviewPanelFooter } from '../host_preview/footer';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface HostPanelProps extends Record {
contextID: string;
@@ -130,7 +131,7 @@ export const HostPanel = ({
const openTabPanel = useCallback(
(tab?: EntityDetailsLeftPanelTab) => {
- telemetry.reportRiskInputsExpandedFlyoutOpened({
+ telemetry.reportEvent(EntityEventTypes.RiskInputsExpandedFlyoutOpened, {
entity: 'host',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx
index 3a60c06e3faea..42c8664b2ac0c 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx
@@ -33,6 +33,7 @@ import { UserDetailsPanelKey } from '../user_details_left';
import { useObservedUser } from './hooks/use_observed_user';
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
import { UserPreviewPanelFooter } from '../user_preview/footer';
+import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface UserPanelProps extends Record {
contextID: string;
@@ -123,7 +124,7 @@ export const UserPanel = ({
const { openLeftPanel } = useExpandableFlyoutApi();
const openPanelTab = useCallback(
(tab?: EntityDetailsLeftPanelTab) => {
- telemetry.reportRiskInputsExpandedFlyoutOpened({
+ telemetry.reportEvent(EntityEventTypes.RiskInputsExpandedFlyoutOpened, {
entity: 'user',
});
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/preview_link.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/preview_link.tsx
index fc51a4e64e6c0..b6a4ea33ba4bc 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/components/preview_link.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/shared/components/preview_link.tsx
@@ -24,6 +24,7 @@ import { UserPreviewPanelKey } from '../../entity_details/user_right';
import { USER_PREVIEW_BANNER } from '../../document_details/right/components/user_entity_overview';
import { NetworkPanelKey, NETWORK_PREVIEW_BANNER } from '../../network_details';
import { RulePreviewPanelKey, RULE_PREVIEW_BANNER } from '../../rule_details/right';
+import { DocumentEventTypes } from '../../../common/lib/telemetry';
const PREVIEW_FIELDS = [HOST_NAME_FIELD_NAME, USER_NAME_FIELD_NAME, SIGNAL_RULE_NAME_FIELD_NAME];
@@ -133,7 +134,7 @@ export const PreviewLink: FC = ({
id: previewParams.id,
params: previewParams.params,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
diff --git a/x-pack/plugins/security_solution/public/notes/components/add_note.tsx b/x-pack/plugins/security_solution/public/notes/components/add_note.tsx
index 78a84064467f6..5d1ef2ce4d8e1 100644
--- a/x-pack/plugins/security_solution/public/notes/components/add_note.tsx
+++ b/x-pack/plugins/security_solution/public/notes/components/add_note.tsx
@@ -28,6 +28,7 @@ import {
userClosedCreateErrorToast,
} from '../store/notes.slice';
import { MarkdownEditor } from '../../common/components/markdown_editor';
+import { NotesEventTypes } from '../../common/lib/telemetry';
export const MARKDOWN_ARIA_LABEL = i18n.translate(
'xpack.securitySolution.notes.addNote.markdownAriaLabel',
@@ -96,7 +97,7 @@ export const AddNote = memo(
if (onNoteAdd) {
onNoteAdd();
}
- telemetry.reportAddNoteFromExpandableFlyoutClicked({
+ telemetry.reportEvent(NotesEventTypes.AddNoteFromExpandableFlyoutClicked, {
isRelatedToATimeline: timelineId != null,
});
setEditorValue('');
diff --git a/x-pack/plugins/security_solution/public/notes/components/open_flyout_button.tsx b/x-pack/plugins/security_solution/public/notes/components/open_flyout_button.tsx
index 85e9e24c6f26e..65e6389fc2fd1 100644
--- a/x-pack/plugins/security_solution/public/notes/components/open_flyout_button.tsx
+++ b/x-pack/plugins/security_solution/public/notes/components/open_flyout_button.tsx
@@ -16,6 +16,7 @@ import { useSourcererDataView } from '../../sourcerer/containers';
import { SourcererScopeName } from '../../sourcerer/store/model';
import { useKibana } from '../../common/lib/kibana';
import { DocumentDetailsRightPanelKey } from '../../flyout/document_details/shared/constants/panel_keys';
+import { DocumentEventTypes } from '../../common/lib/telemetry';
export const OPEN_FLYOUT_BUTTON = i18n.translate(
'xpack.securitySolution.notes.openFlyoutButtonLabel',
@@ -61,7 +62,7 @@ export const OpenFlyoutButtonIcon = memo(
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'right',
});
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx
index dda17e18c087e..2a6597628a26d 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx
@@ -9,6 +9,7 @@ import type { PropsWithChildren } from 'react';
import React, { createContext, useContext, useMemo } from 'react';
import { useKibana } from '../../common/lib/kibana/kibana_react';
import type { OnboardingCardId } from '../constants';
+import { OnboardingHubEventTypes } from '../../common/lib/telemetry';
export interface OnboardingContextValue {
spaceId: string;
@@ -26,19 +27,19 @@ export const OnboardingContextProvider: React.FC ({
spaceId,
reportCardOpen: (cardId, { auto = false } = {}) => {
- telemetry.reportOnboardingHubStepOpen({
+ telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepOpen, {
stepId: cardId,
trigger: auto ? 'navigation' : 'click',
});
},
reportCardComplete: (cardId, { auto = false } = {}) => {
- telemetry.reportOnboardingHubStepFinished({
+ telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepFinished, {
stepId: cardId,
trigger: auto ? 'auto_check' : 'click',
});
},
reportCardLinkClicked: (cardId, linkId: string) => {
- telemetry.reportOnboardingHubStepLinkClicked({
+ telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepLinkClicked, {
originStepId: cardId,
stepLinkId: linkId,
});
diff --git a/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx b/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx
index 67dcc3848f02a..e785e58435432 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx
@@ -28,9 +28,10 @@ import { KibanaServices, useKibana, useToasts, useUiSetting$ } from '../../commo
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index';
import * as i18n from './translations';
-import type {
- ReportDataQualityCheckAllCompletedParams,
- ReportDataQualityIndexCheckedParams,
+import {
+ type ReportDataQualityCheckAllCompletedParams,
+ type ReportDataQualityIndexCheckedParams,
+ DataQualityEventTypes,
} from '../../common/lib/telemetry';
const LOCAL_STORAGE_KEY = 'dataQualityDashboardLastChecked';
@@ -118,14 +119,14 @@ const DataQualityComponent: React.FC = () => {
const reportDataQualityIndexChecked = useCallback(
(params: ReportDataQualityIndexCheckedParams) => {
- telemetry.reportDataQualityIndexChecked(params);
+ telemetry.reportEvent(DataQualityEventTypes.DataQualityIndexChecked, params);
},
[telemetry]
);
const reportDataQualityCheckAllCompleted = useCallback(
(params: ReportDataQualityCheckAllCompletedParams) => {
- telemetry.reportDataQualityCheckAllCompleted(params);
+ telemetry.reportEvent(DataQualityEventTypes.DataQualityCheckAllCompleted, params);
},
[telemetry]
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx
index fe53c36d3f049..19f98aff651fa 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx
@@ -34,6 +34,7 @@ import { SourcererScopeName } from '../../../../sourcerer/store/model';
import { useSourcererDataView } from '../../../../sourcerer/containers';
import { useDeleteNote } from './hooks/use_delete_note';
import { getTimelineNoteSelector } from '../../timeline/tabs/notes/selectors';
+import { DocumentEventTypes } from '../../../../common/lib/telemetry';
export const NotePreviewsContainer = styled.section`
padding-top: ${({ theme }) => `${theme.eui.euiSizeS}`};
@@ -66,7 +67,7 @@ const ToggleEventDetailsButtonComponent: React.FC
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'right',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx
index 602d2353f342f..5c4a592d99a7d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx
@@ -46,6 +46,7 @@ import { useTimelineControlColumn } from '../shared/use_timeline_control_columns
import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left';
import { useNotesInFlyout } from '../../properties/use_notes_in_flyout';
import { NotesFlyout } from '../../properties/notes_flyout';
+import { NotesEventTypes, DocumentEventTypes } from '../../../../../common/lib/telemetry';
import { TimelineRefetch } from '../../refetch_timeline';
export type Props = TimelineTabCommonProps & PropsFromRedux;
@@ -161,10 +162,10 @@ export const EqlTabContentComponent: React.FC = ({
},
},
});
- telemetry.reportOpenNoteInExpandableFlyoutClicked({
+ telemetry.reportEvent(NotesEventTypes.OpenNoteInExpandableFlyoutClicked, {
location: timelineId,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx
index 1f2360daa051d..0b2553d23ac5e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx
@@ -36,6 +36,7 @@ import { useTimelineControlColumn } from '../shared/use_timeline_control_columns
import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left';
import { useNotesInFlyout } from '../../properties/use_notes_in_flyout';
import { NotesFlyout } from '../../properties/notes_flyout';
+import { NotesEventTypes, DocumentEventTypes } from '../../../../../common/lib/telemetry';
import { defaultUdtHeaders } from '../../body/column_headers/default_headers';
interface PinnedFilter {
@@ -190,10 +191,10 @@ export const PinnedTabContentComponent: React.FC = ({
},
},
});
- telemetry.reportOpenNoteInExpandableFlyoutClicked({
+ telemetry.reportEvent(NotesEventTypes.OpenNoteInExpandableFlyoutClicked, {
location: timelineId,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx
index 8ea1db39a3618..ec61c67a3954a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx
@@ -49,6 +49,7 @@ import { useTimelineColumns } from '../shared/use_timeline_columns';
import { useTimelineControlColumn } from '../shared/use_timeline_control_columns';
import { NotesFlyout } from '../../properties/notes_flyout';
import { useNotesInFlyout } from '../../properties/use_notes_in_flyout';
+import { DocumentEventTypes, NotesEventTypes } from '../../../../../common/lib/telemetry';
const compareQueryProps = (prevProps: Props, nextProps: Props) =>
prevProps.kqlMode === nextProps.kqlMode &&
@@ -228,10 +229,10 @@ export const QueryTabContentComponent: React.FC = ({
},
},
});
- telemetry.reportOpenNoteInExpandableFlyoutClicked({
+ telemetry.reportEvent(NotesEventTypes.OpenNoteInExpandableFlyoutClicked, {
location: timelineId,
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'left',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.tsx
index c711208cb6806..67b9fd50eac22 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.tsx
@@ -34,6 +34,7 @@ import { useUserPrivileges } from '../../../../../common/components/user_privile
import { timelineActions, timelineSelectors } from '../../../../store';
import { timelineDefaults } from '../../../../store/defaults';
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
+import { DocumentEventTypes } from '../../../../../common/lib/telemetry';
import { isFullScreen } from '../../helpers';
const FullScreenButtonIcon = styled(EuiButtonIcon)`
@@ -287,7 +288,7 @@ export const useSessionView = ({ scopeId, height }: { scopeId: string; height?:
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'right',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
index 875c147d6a700..fa5b83f23576a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
@@ -48,6 +48,7 @@ import { transformTimelineItemToUnifiedRows } from '../utils';
import { TimelineEventDetailRow } from './timeline_event_detail_row';
import { CustomTimelineDataGridBody } from './custom_timeline_data_grid_body';
import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants';
+import { DocumentEventTypes } from '../../../../../common/lib/telemetry/types';
export const SAMPLE_SIZE_SETTING = 500;
const DataGridMemoized = React.memo(UnifiedDataTable);
@@ -165,7 +166,7 @@ export const TimelineDataTableComponent: React.FC = memo(
},
},
});
- telemetry.reportDetailsFlyoutOpened({
+ telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'right',
});
diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts
index 6ac8b349b74c5..55fce6a46dba8 100644
--- a/x-pack/plugins/security_solution/public/types.ts
+++ b/x-pack/plugins/security_solution/public/types.ts
@@ -84,7 +84,6 @@ import type { Assets } from './assets';
import type { Investigations } from './investigations';
import type { MachineLearning } from './machine_learning';
-import type { TelemetryClientStart } from './common/lib/telemetry';
import type { Dashboards } from './dashboards';
import type { BreadcrumbsNav } from './common/breadcrumbs/types';
import type { TopValuesPopoverService } from './app/components/top_values_popover/top_values_popover_service';
@@ -93,6 +92,7 @@ import type { SetComponents, GetComponents$ } from './contract_components';
import type { ConfigSettings } from '../common/config_settings';
import type { OnboardingService } from './onboarding/service';
import type { SolutionNavigation } from './app/solution_navigation/solution_navigation';
+import type { TelemetryServiceStart } from './common/lib/telemetry';
export interface SetupPlugins {
cloud?: CloudSetup;
@@ -188,7 +188,7 @@ export type StartServices = CoreStart &
getPluginWrapper: () => typeof SecuritySolutionTemplateWrapper;
};
contentManagement: ContentManagementPublicStart;
- telemetry: TelemetryClientStart;
+ telemetry: TelemetryServiceStart;
customDataService: DataPublicPluginStart;
topValuesPopover: TopValuesPopoverService;
timelineDataService: DataPublicPluginStart;
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.ts
index 196ffc71162db..8ba1de0bb70d9 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.ts
@@ -27,7 +27,12 @@ export function registerActionAuditLogRoutes(
.get({
access: 'public',
path: ENDPOINT_ACTION_LOG_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/details.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/details.ts
index 96b466a251cf4..1d524b08aefce 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/details.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/details.ts
@@ -32,7 +32,12 @@ export const registerActionDetailsRoutes = (
.get({
access: 'public',
path: ACTION_DETAILS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts
index 2e16c57886f7d..29aa6f4bba3d8 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts
@@ -38,7 +38,12 @@ export const registerActionFileDownloadRoutes = (
// we need to enable setting the version number via query params
enableQueryVersion: true,
path: ACTION_AGENT_FILE_DOWNLOAD_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.ts
index 1cb4e95e1eaf1..63118a64fc453 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.ts
@@ -74,7 +74,12 @@ export const registerActionFileInfoRoute = (
.get({
access: 'public',
path: ACTION_AGENT_FILE_INFO_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts
index a858909f5e2ed..05e5d77eb945b 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts
@@ -30,7 +30,12 @@ export function registerActionListRoutes(
.get({
access: 'public',
path: BASE_ENDPOINT_ACTION_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts
index b6eb2376bd1cb..0fc90c7589b99 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts
@@ -80,7 +80,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: ISOLATE_HOST_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -99,7 +104,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: UNISOLATE_HOST_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -119,7 +129,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: ISOLATE_HOST_ROUTE_V2,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -139,7 +154,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: UNISOLATE_HOST_ROUTE_V2,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -159,7 +179,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: KILL_PROCESS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -182,7 +207,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: SUSPEND_PROCESS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -205,7 +235,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: GET_PROCESSES_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -225,7 +260,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: GET_FILE_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -245,7 +285,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: EXECUTE_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -265,9 +310,14 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: UPLOAD_ROUTE,
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
options: {
authRequired: true,
- tags: ['access:securitySolution'],
+
body: {
accepts: ['multipart/form-data'],
output: 'stream',
@@ -293,7 +343,12 @@ export function registerResponseActionRoutes(
.post({
access: 'public',
path: SCAN_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/state.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/state.ts
index 3ea4d9fa35753..c2d5b850d6bb2 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/state.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/state.ts
@@ -32,7 +32,12 @@ export function registerActionStateRoutes(
.get({
access: 'public',
path: ACTION_STATE_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts
index 6172bf07d2320..8a245dfb451ea 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.ts
@@ -29,7 +29,12 @@ export function registerActionStatusRoutes(
.get({
access: 'public',
path: ACTION_STATUS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts
index e6ea2f7595785..7ca24156b45fd 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts
@@ -27,7 +27,12 @@ export const registerAgentStatusRoute = (
.get({
access: 'internal',
path: AGENT_STATUS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts
index 2f6e46d1d7727..3f028719fe5ad 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts
@@ -54,7 +54,12 @@ export function registerEndpointRoutes(
.get({
access: 'public',
path: HOST_METADATA_LIST_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -94,7 +99,12 @@ export function registerEndpointRoutes(
.get({
access: 'public',
path: METADATA_TRANSFORMS_STATUS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
// @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo}
deprecated: true,
})
@@ -114,7 +124,12 @@ export function registerEndpointRoutes(
.get({
access: 'internal',
path: METADATA_TRANSFORMS_STATUS_INTERNAL_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
index 00054964e4401..6010c56557273 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
@@ -240,8 +240,8 @@ describe('test endpoint routes', () => {
});
expect(routeConfig.options).toEqual({
authRequired: true,
- tags: ['access:securitySolution'],
});
+ expect(routeConfig.security?.authz).toEqual({ requiredPrivileges: ['securitySolution'] });
expect(mockResponse.ok).toBeCalled();
const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as MetadataListResponse;
expect(endpointResultList.data.length).toEqual(1);
@@ -614,8 +614,8 @@ describe('test endpoint routes', () => {
expect(esClientMock.transform.getTransformStats).toHaveBeenCalledTimes(1);
expect(routeConfig.options).toEqual({
authRequired: true,
- tags: ['access:securitySolution'],
});
+ expect(routeConfig.security?.authz).toEqual({ requiredPrivileges: ['securitySolution'] });
expect(mockResponse.ok).toBeCalled();
const response = mockResponse.ok.mock.calls[0][0]?.body as TransformGetTransformStatsResponse;
expect(response.count).toEqual(expectedResponse.count);
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts
index 7b28ccfcf9fe7..4355684407bb1 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts
@@ -25,7 +25,12 @@ export function registerProtectionUpdatesNoteRoutes(
.post({
access: 'public',
path: PROTECTION_UPDATES_NOTE_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
@@ -45,7 +50,12 @@ export function registerProtectionUpdatesNoteRoutes(
.get({
access: 'public',
path: PROTECTION_UPDATES_NOTE_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts
index 677fb004ee862..bbee33114534b 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts
@@ -42,7 +42,12 @@ export function registerEndpointSuggestionsRoutes(
.post({
access: 'public',
path: SUGGESTIONS_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
// @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo}
deprecated: true,
})
@@ -64,7 +69,12 @@ export function registerEndpointSuggestionsRoutes(
.post({
access: 'internal',
path: SUGGESTIONS_INTERNAL_ROUTE,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ security: {
+ authz: {
+ requiredPrivileges: ['securitySolution'],
+ },
+ },
+ options: { authRequired: true },
})
.addVersion(
{
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/enrich_signal_threat_matches.ts
index 8f98eab1a93e9..0d9882fe8aec3 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/enrich_signal_threat_matches.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/enrich_signal_threat_matches.ts
@@ -24,8 +24,10 @@ export const groupAndMergeSignalMatches = (signalHits: SignalSourceHit[]): Signa
if (existingSignalHit == null) {
acc[signalId] = signalHit;
} else {
- const existingQueries = existingSignalHit?.matched_queries ?? [];
- const newQueries = signalHit.matched_queries ?? [];
+ const existingQueries = Array.isArray(existingSignalHit?.matched_queries)
+ ? existingSignalHit.matched_queries
+ : [];
+ const newQueries = Array.isArray(signalHit.matched_queries) ? signalHit.matched_queries : [];
existingSignalHit.matched_queries = [...existingQueries, ...newQueries];
acc[signalId] = existingSignalHit;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts
index 309516a57335c..9694d37aab0ab 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts
@@ -90,7 +90,9 @@ export async function getSignalsQueryMapFromThreatIndex(
while (maxThreatsReachedMap.size < eventsCount && threatList?.hits.hits.length > 0) {
threatList.hits.hits.forEach((threatHit) => {
- const matchedQueries = threatHit?.matched_queries || [];
+ const matchedQueries = Array.isArray(threatHit?.matched_queries)
+ ? threatHit.matched_queries
+ : [];
matchedQueries.forEach((matchedQuery) => {
const decodedQuery = decodeThreatMatchNamedQuery(matchedQuery);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/utils.ts
index da72d121c371c..347ea5d1d94c6 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/utils.ts
@@ -189,7 +189,9 @@ export const decodeThreatMatchNamedQuery = (encoded: string): DecodedThreatNamed
export const extractNamedQueries = (
hit: SignalSourceHit | ThreatListItem
): DecodedThreatNamedQuery[] =>
- hit.matched_queries?.map((match) => decodeThreatMatchNamedQuery(match)) ?? [];
+ Array.isArray(hit.matched_queries)
+ ? hit.matched_queries.map((match) => decodeThreatMatchNamedQuery(match))
+ : [];
export const buildExecutionIntervalValidator: (interval: string) => () => void = (interval) => {
const intervalDuration = parseInterval(interval);
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts
index e15aceb8a713b..54af298d11a3e 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts
@@ -46,16 +46,19 @@ export const buildIndicatorShouldClauses = (
export const buildIndicatorEnrichments = (hits: estypes.SearchHit[]): CtiEnrichment[] => {
return hits.flatMap(({ matched_queries: matchedQueries, ...hit }) => {
return (
- matchedQueries?.reduce((enrichments, matchedQuery) => {
- if (isValidEventField(matchedQuery)) {
- enrichments.push({
- ...hit.fields,
- ...buildIndicatorMatchedFields(hit, matchedQuery),
- });
- }
+ (Array.isArray(matchedQueries) ? matchedQueries : [])?.reduce(
+ (enrichments, matchedQuery) => {
+ if (isValidEventField(matchedQuery)) {
+ enrichments.push({
+ ...hit.fields,
+ ...buildIndicatorMatchedFields(hit, matchedQuery),
+ });
+ }
- return enrichments;
- }, []) ?? []
+ return enrichments;
+ },
+ []
+ ) ?? []
);
});
};
diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts
index b0f5a4658e7d2..066ab8e8c093e 100644
--- a/x-pack/plugins/serverless_search/public/navigation_tree.ts
+++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts
@@ -20,14 +20,6 @@ export const navigationTree = (): NavigationTreeDefinition => ({
isCollapsible: false,
breadcrumbStatus: 'hidden',
children: [
- {
- id: 'home',
- title: i18n.translate('xpack.serverlessSearch.nav.home', {
- defaultMessage: 'Home',
- }),
- link: 'elasticsearchStart',
- spaceBefore: 'm',
- },
{
id: 'data',
title: i18n.translate('xpack.serverlessSearch.nav.data', {
@@ -47,9 +39,8 @@ export const navigationTree = (): NavigationTreeDefinition => ({
pathNameSerialized.startsWith(
prepend('/app/management/data/index_management/')
) ||
- pathNameSerialized.startsWith(
- prepend('/app/elasticsearch/indices/index_details/')
- )
+ pathNameSerialized.startsWith(prepend('/app/elasticsearch/indices')) ||
+ pathNameSerialized.startsWith(prepend('/app/elasticsearch/start'))
);
},
},
diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts
index 45960195be216..cd820d1e70780 100644
--- a/x-pack/plugins/task_manager/server/plugin.ts
+++ b/x-pack/plugins/task_manager/server/plugin.ts
@@ -29,7 +29,7 @@ import { TaskManagerConfig } from './config';
import { createInitialMiddleware, addMiddlewareToChain, Middleware } from './lib/middleware';
import { removeIfExists } from './lib/remove_if_exists';
import { setupSavedObjects, BACKGROUND_TASK_NODE_SO_NAME, TASK_SO_NAME } from './saved_objects';
-import { TaskDefinitionRegistry, TaskTypeDictionary, REMOVED_TYPES } from './task_type_dictionary';
+import { TaskDefinitionRegistry, TaskTypeDictionary } from './task_type_dictionary';
import { AggregationOpts, FetchResult, SearchOpts, TaskStore } from './task_store';
import { createManagedConfiguration } from './lib/create_managed_configuration';
import { TaskScheduling } from './task_scheduling';
@@ -45,6 +45,10 @@ import { metricsStream, Metrics } from './metrics';
import { TaskManagerMetricsCollector } from './metrics/task_metrics_collector';
import { TaskPartitioner } from './lib/task_partitioner';
import { getDefaultCapacity } from './lib/get_default_capacity';
+import {
+ registerMarkRemovedTasksAsUnrecognizedDefinition,
+ scheduleMarkRemovedTasksAsUnrecognizedDefinition,
+} from './removed_tasks/mark_removed_tasks_as_unrecognized';
export interface TaskManagerSetupContract {
/**
@@ -221,6 +225,11 @@ export class TaskManagerPlugin
}
registerDeleteInactiveNodesTaskDefinition(this.logger, core.getStartServices, this.definitions);
+ registerMarkRemovedTasksAsUnrecognizedDefinition(
+ this.logger,
+ core.getStartServices,
+ this.definitions
+ );
if (this.config.unsafe.exclude_task_types.length) {
this.logger.warn(
@@ -332,7 +341,6 @@ export class TaskManagerPlugin
this.taskPollingLifecycle = new TaskPollingLifecycle({
config: this.config!,
definitions: this.definitions,
- unusedTypes: REMOVED_TYPES,
logger: this.logger,
executionContext,
taskStore,
@@ -384,6 +392,7 @@ export class TaskManagerPlugin
});
scheduleDeleteInactiveNodesTaskDefinition(this.logger, taskScheduling).catch(() => {});
+ scheduleMarkRemovedTasksAsUnrecognizedDefinition(this.logger, taskScheduling).catch(() => {});
return {
fetch: (opts: SearchOpts): Promise => taskStore.fetch(opts),
diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
index 1f244f7f4c8a5..a408bd3f634d9 100644
--- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
+++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
@@ -106,7 +106,6 @@ describe('TaskPollingLifecycle', () => {
},
taskStore: mockTaskStore,
logger: taskManagerLogger,
- unusedTypes: [],
definitions: new TaskTypeDictionary(taskManagerLogger),
middleware: createInitialMiddleware(),
startingCapacity: 20,
diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts
index 0b1710ae7fa2f..fb6776fa34f28 100644
--- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts
+++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts
@@ -55,7 +55,6 @@ export interface ITaskEventEmitter {
export type TaskPollingLifecycleOpts = {
logger: Logger;
definitions: TaskTypeDictionary;
- unusedTypes: string[];
taskStore: TaskStore;
config: TaskManagerConfig;
middleware: Middleware;
@@ -115,7 +114,6 @@ export class TaskPollingLifecycle implements ITaskEventEmitter this.pool.availableCapacity(taskType),
taskPartitioner,
diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts
index 76df8b7ae5584..fa1d1f749985b 100644
--- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts
+++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts
@@ -70,7 +70,6 @@ describe('mark_available_tasks_as_claimed', () => {
fieldUpdates,
claimableTaskTypes: definitions.getAllTypes(),
skippedTaskTypes: [],
- unusedTaskTypes: [],
taskMaxAttempts: Array.from(definitions).reduce((accumulator, [type, { maxAttempts }]) => {
return { ...accumulator, [type]: maxAttempts || defaultMaxAttempts };
}, {}),
@@ -153,8 +152,6 @@ if (doc['task.runAt'].size()!=0) {
ctx._source.task.status = "claiming"; ${Object.keys(fieldUpdates)
.map((field) => `ctx._source.task.${field}=params.fieldUpdates.${field};`)
.join(' ')}
- } else if (params.unusedTaskTypes.contains(ctx._source.task.taskType)) {
- ctx._source.task.status = "unrecognized";
} else {
ctx.op = "noop";
}`,
@@ -167,7 +164,6 @@ if (doc['task.runAt'].size()!=0) {
},
claimableTaskTypes: ['sampleTask', 'otherTask'],
skippedTaskTypes: [],
- unusedTaskTypes: [],
taskMaxAttempts: {
sampleTask: 5,
otherTask: 1,
@@ -242,7 +238,6 @@ if (doc['task.runAt'].size()!=0) {
fieldUpdates,
claimableTaskTypes: ['foo', 'bar'],
skippedTaskTypes: [],
- unusedTaskTypes: [],
taskMaxAttempts: {
foo: 5,
bar: 2,
diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts
index 4e138545aec25..ec99c6ad5bf80 100644
--- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts
+++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts
@@ -202,7 +202,6 @@ export interface UpdateFieldsAndMarkAsFailedOpts {
};
claimableTaskTypes: string[];
skippedTaskTypes: string[];
- unusedTaskTypes: string[];
taskMaxAttempts: { [field: string]: number };
}
@@ -210,7 +209,6 @@ export const updateFieldsAndMarkAsFailed = ({
fieldUpdates,
claimableTaskTypes,
skippedTaskTypes,
- unusedTaskTypes,
taskMaxAttempts,
}: UpdateFieldsAndMarkAsFailedOpts): ScriptClause => {
const setScheduledAtScript = `if(ctx._source.task.retryAt != null && ZonedDateTime.parse(ctx._source.task.retryAt).toInstant().toEpochMilli() < params.now) {
@@ -227,8 +225,6 @@ export const updateFieldsAndMarkAsFailed = ({
source: `
if (params.claimableTaskTypes.contains(ctx._source.task.taskType)) {
${setScheduledAtAndMarkAsClaimed}
- } else if (params.unusedTaskTypes.contains(ctx._source.task.taskType)) {
- ctx._source.task.status = "unrecognized";
} else {
ctx.op = "noop";
}`,
@@ -238,7 +234,6 @@ export const updateFieldsAndMarkAsFailed = ({
fieldUpdates,
claimableTaskTypes,
skippedTaskTypes,
- unusedTaskTypes,
taskMaxAttempts,
},
};
diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts
index 437af8e007bdb..629e3464399c7 100644
--- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts
+++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts
@@ -83,7 +83,6 @@ describe('TaskClaiming', () => {
strategy: 'non-default',
definitions,
excludedTaskTypes: [],
- unusedTypes: [],
taskStore: taskStoreMock.create({ taskManagerId: '' }),
maxAttempts: 2,
getAvailableCapacity: () => 10,
@@ -134,7 +133,6 @@ describe('TaskClaiming', () => {
strategy: 'default',
definitions,
excludedTaskTypes: [],
- unusedTypes: [],
taskStore: taskStoreMock.create({ taskManagerId: '' }),
maxAttempts: 2,
getAvailableCapacity: () => 10,
diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts
index c9bca31755408..1b1e414903628 100644
--- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts
+++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts
@@ -34,7 +34,6 @@ export interface TaskClaimingOpts {
logger: Logger;
strategy: string;
definitions: TaskTypeDictionary;
- unusedTypes: string[];
taskStore: TaskStore;
maxAttempts: number;
excludedTaskTypes: string[];
@@ -92,7 +91,6 @@ export class TaskClaiming {
private readonly taskClaimingBatchesByType: TaskClaimingBatches;
private readonly taskMaxAttempts: Record;
private readonly excludedTaskTypes: string[];
- private readonly unusedTypes: string[];
private readonly taskClaimer: TaskClaimerFn;
private readonly taskPartitioner: TaskPartitioner;
@@ -111,7 +109,6 @@ export class TaskClaiming {
this.taskClaimingBatchesByType = this.partitionIntoClaimingBatches(this.definitions);
this.taskMaxAttempts = Object.fromEntries(this.normalizeMaxAttempts(this.definitions));
this.excludedTaskTypes = opts.excludedTaskTypes;
- this.unusedTypes = opts.unusedTypes;
this.taskClaimer = getTaskClaimer(this.logger, opts.strategy);
this.events$ = new Subject();
this.taskPartitioner = opts.taskPartitioner;
@@ -178,7 +175,6 @@ export class TaskClaiming {
taskStore: this.taskStore,
events$: this.events$,
getCapacity: this.getAvailableCapacity,
- unusedTypes: this.unusedTypes,
definitions: this.definitions,
taskMaxAttempts: this.taskMaxAttempts,
excludedTaskTypes: this.excludedTaskTypes,
diff --git a/x-pack/plugins/task_manager/server/removed_tasks/mark_removed_tasks_as_unrecognized.test.ts b/x-pack/plugins/task_manager/server/removed_tasks/mark_removed_tasks_as_unrecognized.test.ts
new file mode 100644
index 0000000000000..1485216a67f33
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/removed_tasks/mark_removed_tasks_as_unrecognized.test.ts
@@ -0,0 +1,266 @@
+/*
+ * 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 { mockLogger } from '../test_utils';
+import { coreMock, elasticsearchServiceMock } from '@kbn/core/server/mocks';
+import { SCHEDULE_INTERVAL, taskRunner } from './mark_removed_tasks_as_unrecognized';
+import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+const createTaskDoc = (id: string = '1'): SearchHit => ({
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: `task:${id}`,
+ _score: 1,
+ _source: {
+ references: [],
+ type: 'task',
+ updated_at: '2024-11-06T14:17:55.935Z',
+ task: {
+ taskType: 'report',
+ params: '{}',
+ state: '{"foo":"test"}',
+ stateVersion: 1,
+ runAt: '2024-11-06T14:17:55.935Z',
+ enabled: true,
+ scheduledAt: '2024-11-06T14:17:55.935Z',
+ attempts: 0,
+ status: 'idle',
+ startedAt: null,
+ retryAt: null,
+ ownerId: null,
+ partition: 211,
+ },
+ },
+});
+
+describe('markRemovedTasksAsUnrecognizedTask', () => {
+ const logger = mockLogger();
+ const coreSetup = coreMock.createSetup();
+ const esClient = elasticsearchServiceMock.createStart();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('marks removed tasks as unrecognized', async () => {
+ esClient.client.asInternalUser.bulk.mockResolvedValue({
+ errors: false,
+ took: 0,
+ items: [
+ {
+ update: {
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: 'task:123',
+ _version: 2,
+ result: 'updated',
+ _shards: { total: 1, successful: 1, failed: 0 },
+ _seq_no: 84,
+ _primary_term: 1,
+ status: 200,
+ },
+ },
+ {
+ update: {
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: 'task:456',
+ _version: 2,
+ result: 'updated',
+ _shards: { total: 1, successful: 1, failed: 0 },
+ _seq_no: 84,
+ _primary_term: 1,
+ status: 200,
+ },
+ },
+ {
+ update: {
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: 'task:789',
+ _version: 2,
+ result: 'updated',
+ _shards: { total: 1, successful: 1, failed: 0 },
+ _seq_no: 84,
+ _primary_term: 1,
+ status: 200,
+ },
+ },
+ ],
+ });
+
+ coreSetup.getStartServices.mockResolvedValue([
+ {
+ ...coreMock.createStart(),
+ elasticsearch: esClient,
+ },
+ {},
+ coreMock.createSetup(),
+ ]);
+ // @ts-expect-error
+ esClient.client.asInternalUser.search.mockResponse({
+ hits: { hits: [createTaskDoc('123'), createTaskDoc('456'), createTaskDoc('789')], total: 3 },
+ });
+
+ const runner = taskRunner(logger, coreSetup.getStartServices)();
+ const result = await runner.run();
+
+ expect(esClient.client.asInternalUser.bulk).toHaveBeenCalledWith({
+ body: [
+ { update: { _id: 'task:123' } },
+ { doc: { task: { status: 'unrecognized' } } },
+ { update: { _id: 'task:456' } },
+ { doc: { task: { status: 'unrecognized' } } },
+ { update: { _id: 'task:789' } },
+ { doc: { task: { status: 'unrecognized' } } },
+ ],
+ index: '.kibana_task_manager',
+ refresh: false,
+ });
+
+ expect(logger.debug).toHaveBeenCalledWith(`Marked 3 removed tasks as unrecognized`);
+
+ expect(result).toEqual({
+ state: {},
+ schedule: { interval: SCHEDULE_INTERVAL },
+ });
+ });
+
+ it('skips update when there are no removed task types', async () => {
+ coreSetup.getStartServices.mockResolvedValue([
+ {
+ ...coreMock.createStart(),
+ elasticsearch: esClient,
+ },
+ {},
+ coreMock.createSetup(),
+ ]);
+ // @ts-expect-error
+ esClient.client.asInternalUser.search.mockResponse({
+ hits: { hits: [], total: 0 },
+ });
+
+ const runner = taskRunner(logger, coreSetup.getStartServices)();
+ const result = await runner.run();
+
+ expect(esClient.client.asInternalUser.bulk).not.toHaveBeenCalled();
+
+ expect(result).toEqual({
+ state: {},
+ schedule: { interval: SCHEDULE_INTERVAL },
+ });
+ });
+
+ it('schedules the next run even when there is an error', async () => {
+ coreSetup.getStartServices.mockResolvedValue([
+ {
+ ...coreMock.createStart(),
+ elasticsearch: esClient,
+ },
+ {},
+ coreMock.createSetup(),
+ ]);
+ esClient.client.asInternalUser.search.mockRejectedValueOnce(new Error('foo'));
+
+ const runner = taskRunner(logger, coreSetup.getStartServices)();
+ const result = await runner.run();
+
+ expect(esClient.client.asInternalUser.bulk).not.toHaveBeenCalled();
+
+ expect(logger.error).toHaveBeenCalledWith(
+ 'Failed to mark removed tasks as unrecognized. Error: foo'
+ );
+
+ expect(result).toEqual({
+ state: {},
+ schedule: { interval: SCHEDULE_INTERVAL },
+ });
+ });
+
+ it('handles partial errors from bulk partial update', async () => {
+ esClient.client.asInternalUser.bulk.mockResolvedValue({
+ errors: false,
+ took: 0,
+ items: [
+ {
+ update: {
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: 'task:123',
+ _version: 2,
+ result: 'updated',
+ _shards: { total: 1, successful: 1, failed: 0 },
+ _seq_no: 84,
+ _primary_term: 1,
+ status: 200,
+ },
+ },
+ {
+ update: {
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: 'task:456',
+ _version: 2,
+ result: 'updated',
+ _shards: { total: 1, successful: 1, failed: 0 },
+ _seq_no: 84,
+ _primary_term: 1,
+ status: 200,
+ },
+ },
+ {
+ update: {
+ _index: '.kibana_task_manager_9.0.0_001',
+ _id: 'task:789',
+ _version: 2,
+ error: {
+ type: 'document_missing_exception',
+ reason: '[5]: document missing',
+ index_uuid: 'aAsFqTI0Tc2W0LCWgPNrOA',
+ shard: '0',
+ index: '.kibana_task_manager_9.0.0_001',
+ },
+ status: 404,
+ },
+ },
+ ],
+ });
+
+ coreSetup.getStartServices.mockResolvedValue([
+ {
+ ...coreMock.createStart(),
+ elasticsearch: esClient,
+ },
+ {},
+ coreMock.createSetup(),
+ ]);
+ // @ts-expect-error
+ esClient.client.asInternalUser.search.mockResponse({
+ hits: { hits: [createTaskDoc('123'), createTaskDoc('456'), createTaskDoc('789')], total: 3 },
+ });
+
+ const runner = taskRunner(logger, coreSetup.getStartServices)();
+ const result = await runner.run();
+
+ expect(esClient.client.asInternalUser.bulk).toHaveBeenCalledWith({
+ body: [
+ { update: { _id: 'task:123' } },
+ { doc: { task: { status: 'unrecognized' } } },
+ { update: { _id: 'task:456' } },
+ { doc: { task: { status: 'unrecognized' } } },
+ { update: { _id: 'task:789' } },
+ { doc: { task: { status: 'unrecognized' } } },
+ ],
+ index: '.kibana_task_manager',
+ refresh: false,
+ });
+ expect(logger.warn).toHaveBeenCalledWith(
+ `Error updating task task:789 to mark as unrecognized - {\"type\":\"document_missing_exception\",\"reason\":\"[5]: document missing\",\"index_uuid\":\"aAsFqTI0Tc2W0LCWgPNrOA\",\"shard\":\"0\",\"index\":\".kibana_task_manager_9.0.0_001\"}`
+ );
+
+ expect(logger.debug).toHaveBeenCalledWith(`Marked 2 removed tasks as unrecognized`);
+
+ expect(result).toEqual({
+ state: {},
+ schedule: { interval: SCHEDULE_INTERVAL },
+ });
+ });
+});
diff --git a/x-pack/plugins/task_manager/server/removed_tasks/mark_removed_tasks_as_unrecognized.ts b/x-pack/plugins/task_manager/server/removed_tasks/mark_removed_tasks_as_unrecognized.ts
new file mode 100644
index 0000000000000..e28d5221e72d5
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/removed_tasks/mark_removed_tasks_as_unrecognized.ts
@@ -0,0 +1,150 @@
+/*
+ * 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 { Logger } from '@kbn/logging';
+import { CoreStart } from '@kbn/core-lifecycle-server';
+import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import { TaskScheduling } from '../task_scheduling';
+import { TaskTypeDictionary } from '../task_type_dictionary';
+import { ConcreteTaskInstance, TaskManagerStartContract } from '..';
+import { TaskStatus } from '../task';
+import { REMOVED_TYPES } from '../task_type_dictionary';
+import { TASK_MANAGER_INDEX } from '../constants';
+
+export const TASK_ID = 'mark_removed_tasks_as_unrecognized';
+const TASK_TYPE = `task_manager:${TASK_ID}`;
+
+export const SCHEDULE_INTERVAL = '1h';
+
+export async function scheduleMarkRemovedTasksAsUnrecognizedDefinition(
+ logger: Logger,
+ taskScheduling: TaskScheduling
+) {
+ try {
+ await taskScheduling.ensureScheduled({
+ id: TASK_ID,
+ taskType: TASK_TYPE,
+ schedule: { interval: SCHEDULE_INTERVAL },
+ state: {},
+ params: {},
+ });
+ } catch (e) {
+ logger.error(`Error scheduling ${TASK_ID} task, received ${e.message}`);
+ }
+}
+
+export function registerMarkRemovedTasksAsUnrecognizedDefinition(
+ logger: Logger,
+ coreStartServices: () => Promise<[CoreStart, TaskManagerStartContract, unknown]>,
+ taskTypeDictionary: TaskTypeDictionary
+) {
+ taskTypeDictionary.registerTaskDefinitions({
+ [TASK_TYPE]: {
+ title: 'Mark removed tasks as unrecognized',
+ createTaskRunner: taskRunner(logger, coreStartServices),
+ },
+ });
+}
+
+export function taskRunner(
+ logger: Logger,
+ coreStartServices: () => Promise<[CoreStart, TaskManagerStartContract, unknown]>
+) {
+ return () => {
+ return {
+ async run() {
+ try {
+ const [{ elasticsearch }] = await coreStartServices();
+ const esClient = elasticsearch.client.asInternalUser;
+
+ const removedTasks = await queryForRemovedTasks(esClient);
+
+ if (removedTasks.length > 0) {
+ await updateTasksToBeUnrecognized(esClient, logger, removedTasks);
+ }
+
+ return {
+ state: {},
+ schedule: { interval: SCHEDULE_INTERVAL },
+ };
+ } catch (e) {
+ logger.error(`Failed to mark removed tasks as unrecognized. Error: ${e.message}`);
+ return {
+ state: {},
+ schedule: { interval: SCHEDULE_INTERVAL },
+ };
+ }
+ },
+ };
+ };
+}
+
+async function queryForRemovedTasks(
+ esClient: ElasticsearchClient
+): Promise>> {
+ const result = await esClient.search({
+ index: TASK_MANAGER_INDEX,
+ body: {
+ size: 100,
+ _source: false,
+ query: {
+ bool: {
+ must: [
+ {
+ terms: {
+ 'task.taskType': REMOVED_TYPES,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return result.hits.hits;
+}
+
+async function updateTasksToBeUnrecognized(
+ esClient: ElasticsearchClient,
+ logger: Logger,
+ removedTasks: Array>
+) {
+ const bulkBody = [];
+ for (const task of removedTasks) {
+ bulkBody.push({ update: { _id: task._id } });
+ bulkBody.push({ doc: { task: { status: TaskStatus.Unrecognized } } });
+ }
+
+ let removedCount = 0;
+ try {
+ const removeResults = await esClient.bulk({
+ index: TASK_MANAGER_INDEX,
+ refresh: false,
+ body: bulkBody,
+ });
+ for (const removeResult of removeResults.items) {
+ if (!removeResult.update || !removeResult.update._id) {
+ logger.warn(
+ `Error updating task with unknown to mark as unrecognized - malformed response`
+ );
+ } else if (removeResult.update?.error) {
+ logger.warn(
+ `Error updating task ${
+ removeResult.update._id
+ } to mark as unrecognized - ${JSON.stringify(removeResult.update.error)}`
+ );
+ } else {
+ removedCount++;
+ }
+ }
+ logger.debug(`Marked ${removedCount} removed tasks as unrecognized`);
+ } catch (err) {
+ // don't worry too much about errors, we'll try again next time
+ logger.warn(`Error updating tasks to mark as unrecognized: ${err}`);
+ }
+}
diff --git a/x-pack/plugins/task_manager/server/task_claimers/index.ts b/x-pack/plugins/task_manager/server/task_claimers/index.ts
index 178ebacf68cb9..f41c489fd7550 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/index.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/index.ts
@@ -26,7 +26,6 @@ export interface TaskClaimerOpts {
events$: Subject;
taskStore: TaskStore;
definitions: TaskTypeDictionary;
- unusedTypes: string[];
excludedTaskTypes: string[];
taskMaxAttempts: Record;
logger: Logger;
diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts
index fe44ce9e94c68..07dae3c48a392 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts
@@ -190,7 +190,6 @@ describe('TaskClaiming', () => {
definitions,
taskStore: store,
excludedTaskTypes,
- unusedTypes: unusedTaskTypes,
maxAttempts: taskClaimingOpts.maxAttempts ?? 2,
getAvailableCapacity: taskClaimingOpts.getAvailableCapacity ?? (() => 10),
taskPartitioner,
@@ -206,20 +205,17 @@ describe('TaskClaiming', () => {
claimingOpts,
hits = [generateFakeTasks(1)],
excludedTaskTypes = [],
- unusedTaskTypes = [],
}: {
storeOpts: Partial;
taskClaimingOpts: Partial;
claimingOpts: Omit;
hits?: ConcreteTaskInstance[][];
excludedTaskTypes?: string[];
- unusedTaskTypes?: string[];
}) {
const { taskClaiming, store } = initialiseTestClaiming({
storeOpts,
taskClaimingOpts,
excludedTaskTypes,
- unusedTaskTypes,
hits,
});
@@ -355,7 +351,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -378,7 +373,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 3; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 3; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
@@ -440,312 +435,6 @@ describe('TaskClaiming', () => {
expect(result.docs.length).toEqual(3);
});
- test('should not claim tasks of removed type', async () => {
- const store = taskStoreMock.create({ taskManagerId: 'test-test' });
- store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`));
-
- const fetchedTasks = [
- mockInstance({ id: `id-1`, taskType: 'report' }),
- mockInstance({ id: `id-2`, taskType: 'report' }),
- mockInstance({ id: `id-3`, taskType: 'yawn' }),
- ];
-
- const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks);
- store.msearch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap });
- store.getDocVersions.mockResolvedValueOnce(docLatestVersions);
-
- store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk));
- store.bulkPartialUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(getPartialUpdateResult));
- store.bulkPartialUpdate.mockResolvedValueOnce(
- [fetchedTasks[0], fetchedTasks[1]].map(getPartialUpdateResult)
- );
-
- const taskClaiming = new TaskClaiming({
- logger: taskManagerLogger,
- strategy: CLAIM_STRATEGY_MGET,
- definitions: taskDefinitions,
- taskStore: store,
- excludedTaskTypes: [],
- unusedTypes: ['report'],
- maxAttempts: 2,
- getAvailableCapacity: () => 10,
- taskPartitioner,
- });
-
- const resultOrErr = await taskClaiming.claimAvailableTasksIfCapacityIsAvailable({
- claimOwnershipUntil: new Date(),
- });
-
- if (!isOk(resultOrErr)) {
- expect(resultOrErr).toBe(undefined);
- }
-
- const result = unwrap(resultOrErr) as ClaimOwnershipResult;
-
- expect(apm.startTransaction).toHaveBeenCalledWith(
- TASK_MANAGER_MARK_AS_CLAIMED,
- TASK_MANAGER_TRANSACTION_TYPE
- );
- expect(mockApmTrans.end).toHaveBeenCalledWith('success');
-
- expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 2;',
- { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
- );
-
- expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
- size: 40,
- seq_no_primary_term: true,
- });
- expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']);
- expect(store.bulkPartialUpdate).toHaveBeenCalledTimes(2);
- expect(store.bulkPartialUpdate).toHaveBeenNthCalledWith(1, [
- {
- id: fetchedTasks[2].id,
- version: fetchedTasks[2].version,
- scheduledAt: fetchedTasks[2].runAt,
- attempts: 1,
- ownerId: 'test-test',
- retryAt: new Date('1970-01-01T00:05:30.000Z'),
- status: 'running',
- startedAt: new Date('1970-01-01T00:00:00.000Z'),
- },
- ]);
- expect(store.bulkPartialUpdate).toHaveBeenNthCalledWith(2, [
- {
- id: fetchedTasks[0].id,
- version: fetchedTasks[0].version,
- status: 'unrecognized',
- },
- {
- id: fetchedTasks[1].id,
- version: fetchedTasks[1].version,
- status: 'unrecognized',
- },
- ]);
- expect(store.bulkGet).toHaveBeenCalledWith(['id-3']);
-
- expect(result.stats).toEqual({
- tasksClaimed: 1,
- tasksConflicted: 0,
- tasksErrors: 0,
- tasksUpdated: 1,
- tasksLeftUnclaimed: 0,
- staleTasks: 0,
- });
- expect(result.docs.length).toEqual(1);
- });
-
- test('should log warning if error updating single removed task as unrecognized', async () => {
- const store = taskStoreMock.create({ taskManagerId: 'test-test' });
- store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`));
-
- const fetchedTasks = [
- mockInstance({ id: `id-1`, taskType: 'report' }),
- mockInstance({ id: `id-2`, taskType: 'report' }),
- mockInstance({ id: `id-3`, taskType: 'yawn' }),
- ];
-
- const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks);
- store.msearch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap });
- store.getDocVersions.mockResolvedValueOnce(docLatestVersions);
-
- store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk));
- store.bulkPartialUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(getPartialUpdateResult));
- store.bulkPartialUpdate.mockResolvedValueOnce([
- asOk(fetchedTasks[0]),
- asErr({
- type: 'task',
- id: fetchedTasks[1].id,
- status: 404,
- error: {
- type: 'document_missing_exception',
- reason: '[5]: document missing',
- index_uuid: 'aAsFqTI0Tc2W0LCWgPNrOA',
- shard: '0',
- index: '.kibana_task_manager_8.16.0_001',
- },
- }),
- ]);
-
- const taskClaiming = new TaskClaiming({
- logger: taskManagerLogger,
- strategy: CLAIM_STRATEGY_MGET,
- definitions: taskDefinitions,
- taskStore: store,
- excludedTaskTypes: [],
- unusedTypes: ['report'],
- maxAttempts: 2,
- getAvailableCapacity: () => 10,
- taskPartitioner,
- });
-
- const resultOrErr = await taskClaiming.claimAvailableTasksIfCapacityIsAvailable({
- claimOwnershipUntil: new Date(),
- });
-
- if (!isOk(resultOrErr)) {
- expect(resultOrErr).toBe(undefined);
- }
-
- const result = unwrap(resultOrErr) as ClaimOwnershipResult;
-
- expect(apm.startTransaction).toHaveBeenCalledWith(
- TASK_MANAGER_MARK_AS_CLAIMED,
- TASK_MANAGER_TRANSACTION_TYPE
- );
- expect(mockApmTrans.end).toHaveBeenCalledWith('success');
-
- expect(taskManagerLogger.warn).toHaveBeenCalledWith(
- 'Error updating task id-2:task to mark as unrecognized during claim: {"type":"document_missing_exception","reason":"[5]: document missing","index_uuid":"aAsFqTI0Tc2W0LCWgPNrOA","shard":"0","index":".kibana_task_manager_8.16.0_001"}',
- { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
- );
- expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 1;',
- { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
- );
-
- expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
- size: 40,
- seq_no_primary_term: true,
- });
- expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']);
- expect(store.bulkPartialUpdate).toHaveBeenCalledTimes(2);
- expect(store.bulkPartialUpdate).toHaveBeenNthCalledWith(1, [
- {
- id: fetchedTasks[2].id,
- version: fetchedTasks[2].version,
- scheduledAt: fetchedTasks[2].runAt,
- attempts: 1,
- ownerId: 'test-test',
- retryAt: new Date('1970-01-01T00:05:30.000Z'),
- status: 'running',
- startedAt: new Date('1970-01-01T00:00:00.000Z'),
- },
- ]);
- expect(store.bulkPartialUpdate).toHaveBeenNthCalledWith(2, [
- {
- id: fetchedTasks[0].id,
- version: fetchedTasks[0].version,
- status: 'unrecognized',
- },
- {
- id: fetchedTasks[1].id,
- version: fetchedTasks[1].version,
- status: 'unrecognized',
- },
- ]);
- expect(store.bulkGet).toHaveBeenCalledWith(['id-3']);
-
- expect(result.stats).toEqual({
- tasksClaimed: 1,
- tasksConflicted: 0,
- tasksErrors: 0,
- tasksUpdated: 1,
- tasksLeftUnclaimed: 0,
- staleTasks: 0,
- });
- expect(result.docs.length).toEqual(1);
- });
-
- test('should log warning if error updating all removed tasks as unrecognized', async () => {
- const store = taskStoreMock.create({ taskManagerId: 'test-test' });
- store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`));
-
- const fetchedTasks = [
- mockInstance({ id: `id-1`, taskType: 'report' }),
- mockInstance({ id: `id-2`, taskType: 'report' }),
- mockInstance({ id: `id-3`, taskType: 'yawn' }),
- ];
-
- const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks);
- store.msearch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap });
- store.getDocVersions.mockResolvedValueOnce(docLatestVersions);
-
- store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk));
- store.bulkPartialUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(getPartialUpdateResult));
- store.bulkPartialUpdate.mockRejectedValueOnce(new Error('Oh no'));
-
- const taskClaiming = new TaskClaiming({
- logger: taskManagerLogger,
- strategy: CLAIM_STRATEGY_MGET,
- definitions: taskDefinitions,
- taskStore: store,
- excludedTaskTypes: [],
- unusedTypes: ['report'],
- maxAttempts: 2,
- getAvailableCapacity: () => 10,
- taskPartitioner,
- });
-
- const resultOrErr = await taskClaiming.claimAvailableTasksIfCapacityIsAvailable({
- claimOwnershipUntil: new Date(),
- });
-
- if (!isOk(resultOrErr)) {
- expect(resultOrErr).toBe(undefined);
- }
-
- const result = unwrap(resultOrErr) as ClaimOwnershipResult;
-
- expect(apm.startTransaction).toHaveBeenCalledWith(
- TASK_MANAGER_MARK_AS_CLAIMED,
- TASK_MANAGER_TRANSACTION_TYPE
- );
- expect(mockApmTrans.end).toHaveBeenCalledWith('success');
-
- expect(taskManagerLogger.warn).toHaveBeenCalledWith(
- 'Error updating tasks to mark as unrecognized during claim: Error: Oh no',
- { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
- );
- expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
- );
-
- expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
- size: 40,
- seq_no_primary_term: true,
- });
- expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']);
- expect(store.bulkGet).toHaveBeenCalledWith(['id-3']);
- expect(store.bulkPartialUpdate).toHaveBeenCalledTimes(2);
- expect(store.bulkPartialUpdate).toHaveBeenNthCalledWith(1, [
- {
- id: fetchedTasks[2].id,
- version: fetchedTasks[2].version,
- scheduledAt: fetchedTasks[2].runAt,
- attempts: 1,
- ownerId: 'test-test',
- retryAt: new Date('1970-01-01T00:05:30.000Z'),
- status: 'running',
- startedAt: new Date('1970-01-01T00:00:00.000Z'),
- },
- ]);
- expect(store.bulkPartialUpdate).toHaveBeenNthCalledWith(2, [
- {
- id: fetchedTasks[0].id,
- version: fetchedTasks[0].version,
- status: 'unrecognized',
- },
- {
- id: fetchedTasks[1].id,
- version: fetchedTasks[1].version,
- status: 'unrecognized',
- },
- ]);
-
- expect(result.stats).toEqual({
- tasksClaimed: 1,
- tasksConflicted: 0,
- tasksErrors: 0,
- tasksUpdated: 1,
- tasksLeftUnclaimed: 0,
- staleTasks: 0,
- });
- expect(result.docs.length).toEqual(1);
- });
-
test('should handle no tasks to claim', async () => {
const store = taskStoreMock.create({ taskManagerId: 'test-test' });
store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`));
@@ -761,7 +450,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -828,7 +516,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -851,7 +538,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
@@ -922,7 +609,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -945,7 +631,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
@@ -1016,7 +702,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1039,7 +724,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 2; stale: 1; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 2; stale: 1; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
@@ -1116,7 +801,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1139,7 +823,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 4; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 4; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
@@ -1248,7 +932,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1271,7 +954,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 1; removed: 0;',
+ 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 1;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.error).toHaveBeenCalledWith(
@@ -1377,7 +1060,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1400,7 +1082,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 3; stale: 0; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 3; stale: 0; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.warn).toHaveBeenCalledWith(
@@ -1504,7 +1186,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1619,7 +1300,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1642,7 +1322,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 1; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 1; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.error).toHaveBeenCalledWith(
@@ -1753,7 +1433,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -1776,7 +1455,7 @@ describe('TaskClaiming', () => {
expect(mockApmTrans.end).toHaveBeenCalledWith('success');
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
- 'task claimer claimed: 3; stale: 0; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
+ 'task claimer claimed: 3; stale: 0; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0;',
{ tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.error).not.toHaveBeenCalled();
@@ -1870,7 +1549,6 @@ describe('TaskClaiming', () => {
definitions: taskDefinitions,
taskStore: store,
excludedTaskTypes: [],
- unusedTypes: [],
maxAttempts: 2,
getAvailableCapacity: () => 10,
taskPartitioner,
@@ -2488,7 +2166,6 @@ describe('TaskClaiming', () => {
strategy: CLAIM_STRATEGY_MGET,
definitions,
excludedTaskTypes: [],
- unusedTypes: [],
taskStore,
maxAttempts: 2,
getAvailableCapacity,
diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts
index 16d9ba5c7fae7..431daab8dd2cb 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts
@@ -57,7 +57,6 @@ interface OwnershipClaimingOpts {
claimOwnershipUntil: Date;
size: number;
taskTypes: Set;
- removedTypes: Set;
getCapacity: (taskType?: string | undefined) => number;
excludedTaskTypePatterns: string[];
taskStore: TaskStore;
@@ -90,19 +89,16 @@ export async function claimAvailableTasksMget(
async function claimAvailableTasks(opts: TaskClaimerOpts): Promise {
const { getCapacity, claimOwnershipUntil, batches, events$, taskStore, taskPartitioner } = opts;
- const { definitions, unusedTypes, excludedTaskTypes, taskMaxAttempts } = opts;
+ const { definitions, excludedTaskTypes, taskMaxAttempts } = opts;
const logger = createWrappedLogger({ logger: opts.logger, tags: [claimAvailableTasksMget.name] });
const initialCapacity = getCapacity();
const stopTaskTimer = startTaskTimer();
- const removedTypes = new Set(unusedTypes); // REMOVED_TYPES
-
// get a list of candidate tasks to claim, with their version info
const { docs, versionMap } = await searchAvailableTasks({
definitions,
taskTypes: new Set(definitions.getAllTypes()),
excludedTaskTypePatterns: excludedTaskTypes,
- removedTypes,
taskStore,
events$,
claimOwnershipUntil,
@@ -125,18 +121,12 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise `task:${doc.id}`));
- // filter out stale, missing and removed tasks
+ // filter out stale and missing tasks
const currentTasks: ConcreteTaskInstance[] = [];
const staleTasks: ConcreteTaskInstance[] = [];
const missingTasks: ConcreteTaskInstance[] = [];
- const removedTasks: ConcreteTaskInstance[] = [];
for (const searchDoc of docs) {
- if (removedTypes.has(searchDoc.taskType)) {
- removedTasks.push(searchDoc);
- continue;
- }
-
const searchVersion = versionMap.get(searchDoc.id);
const latestVersion = docLatestVersions.get(`task:${searchDoc.id}`);
if (!searchVersion || !latestVersion) {
@@ -236,42 +226,8 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise 0) {
- const tasksToRemove = Array.from(removedTasks);
- const tasksToRemoveUpdates: PartialConcreteTaskInstance[] = [];
- for (const task of tasksToRemove) {
- tasksToRemoveUpdates.push({
- id: task.id,
- status: TaskStatus.Unrecognized,
- });
- }
-
- // don't worry too much about errors, we'll get them next time
- try {
- const removeResults = await taskStore.bulkPartialUpdate(tasksToRemoveUpdates);
- for (const removeResult of removeResults) {
- if (isOk(removeResult)) {
- removedCount++;
- } else {
- const { id, type, error } = removeResult.error;
- logger.warn(
- `Error updating task ${id}:${type} to mark as unrecognized during claim: ${JSON.stringify(
- error
- )}`
- );
- }
- }
- } catch (err) {
- // swallow the error because this is unrelated to the claim cycle
- logger.warn(`Error updating tasks to mark as unrecognized during claim: ${err}`);
- }
- }
-
// TODO: need a better way to generate stats
- const message = `task claimer claimed: ${fullTasksToRun.length}; stale: ${staleTasks.length}; conflicts: ${conflicts}; missing: ${missingTasks.length}; capacity reached: ${leftOverTasks.length}; updateErrors: ${bulkUpdateErrors}; getErrors: ${bulkGetErrors}; removed: ${removedCount};`;
+ const message = `task claimer claimed: ${fullTasksToRun.length}; stale: ${staleTasks.length}; conflicts: ${conflicts}; missing: ${missingTasks.length}; capacity reached: ${leftOverTasks.length}; updateErrors: ${bulkUpdateErrors}; getErrors: ${bulkGetErrors};`;
logger.debug(message);
// build results
@@ -306,7 +262,6 @@ export const NO_ASSIGNED_PARTITIONS_WARNING_INTERVAL = 60000;
async function searchAvailableTasks({
definitions,
taskTypes,
- removedTypes,
excludedTaskTypePatterns,
taskStore,
getCapacity,
@@ -318,7 +273,6 @@ async function searchAvailableTasks({
const claimPartitions = buildClaimPartitions({
types: taskTypes,
excludedTaskTypes,
- removedTypes,
getCapacity,
definitions,
});
@@ -352,10 +306,7 @@ async function searchAvailableTasks({
// Task must be enabled
EnabledTask,
// a task type that's not excluded (may be removed or not)
- OneOfTaskTypes(
- 'task.taskType',
- claimPartitions.unlimitedTypes.concat(Array.from(removedTypes))
- ),
+ OneOfTaskTypes('task.taskType', claimPartitions.unlimitedTypes),
// Either a task with idle status and runAt <= now or
// status running or claiming with a retryAt <= now.
shouldBeOneOf(IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt),
@@ -407,7 +358,6 @@ async function searchAvailableTasks({
}
interface ClaimPartitions {
- removedTypes: string[];
unlimitedTypes: string[];
limitedTypes: Map;
}
@@ -415,30 +365,23 @@ interface ClaimPartitions {
interface BuildClaimPartitionsOpts {
types: Set;
excludedTaskTypes: Set;
- removedTypes: Set;
getCapacity: (taskType?: string) => number;
definitions: TaskTypeDictionary;
}
function buildClaimPartitions(opts: BuildClaimPartitionsOpts): ClaimPartitions {
const result: ClaimPartitions = {
- removedTypes: [],
unlimitedTypes: [],
limitedTypes: new Map(),
};
- const { types, excludedTaskTypes, removedTypes, getCapacity, definitions } = opts;
+ const { types, excludedTaskTypes, getCapacity, definitions } = opts;
for (const type of types) {
const definition = definitions.get(type);
if (definition == null) continue;
if (excludedTaskTypes.has(type)) continue;
- if (removedTypes.has(type)) {
- result.removedTypes.push(type);
- continue;
- }
-
if (definition.maxConcurrency == null) {
result.unlimitedTypes.push(definition.type);
continue;
diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts
index 13e6faf2de0fd..623693e71c54d 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts
@@ -99,14 +99,12 @@ describe('TaskClaiming', () => {
hits = [generateFakeTasks(1)],
versionConflicts = 2,
excludedTaskTypes = [],
- unusedTaskTypes = [],
}: {
storeOpts: Partial;
taskClaimingOpts: Partial;
hits?: ConcreteTaskInstance[][];
versionConflicts?: number;
excludedTaskTypes?: string[];
- unusedTaskTypes?: string[];
}) {
const definitions = storeOpts.definitions ?? taskDefinitions;
const store = taskStoreMock.create({ taskManagerId: storeOpts.taskManagerId });
@@ -136,7 +134,6 @@ describe('TaskClaiming', () => {
definitions,
taskStore: store,
excludedTaskTypes,
- unusedTypes: unusedTaskTypes,
maxAttempts: taskClaimingOpts.maxAttempts ?? 2,
getAvailableCapacity: taskClaimingOpts.getAvailableCapacity ?? (() => 10),
taskPartitioner,
@@ -153,7 +150,6 @@ describe('TaskClaiming', () => {
hits = [generateFakeTasks(1)],
versionConflicts = 2,
excludedTaskTypes = [],
- unusedTaskTypes = [],
}: {
storeOpts: Partial;
taskClaimingOpts: Partial;
@@ -161,14 +157,12 @@ describe('TaskClaiming', () => {
hits?: ConcreteTaskInstance[][];
versionConflicts?: number;
excludedTaskTypes?: string[];
- unusedTaskTypes?: string[];
}) {
const getCapacity = taskClaimingOpts.getAvailableCapacity ?? (() => 10);
const { taskClaiming, store } = initialiseTestClaiming({
storeOpts,
taskClaimingOpts,
excludedTaskTypes,
- unusedTaskTypes,
hits,
versionConflicts,
});
@@ -471,7 +465,6 @@ if (doc['task.runAt'].size()!=0) {
'anotherLimitedToOne',
'limitedToTwo',
],
- unusedTaskTypes: [],
taskMaxAttempts: {
unlimited: maxAttempts,
},
@@ -493,7 +486,6 @@ if (doc['task.runAt'].size()!=0) {
'anotherLimitedToOne',
'limitedToTwo',
],
- unusedTaskTypes: [],
taskMaxAttempts: {
limitedToOne: maxAttempts,
},
@@ -640,7 +632,6 @@ if (doc['task.runAt'].size()!=0) {
},
taskPartitioner,
excludedTaskTypes: [],
- unusedTypes: [],
});
const resultOrErr = await taskClaiming.claimAvailableTasksIfCapacityIsAvailable({
@@ -848,129 +839,6 @@ if (doc['task.runAt'].size()!=0) {
expect(firstCycle).not.toMatchObject(secondCycle);
});
- test('it passes any unusedTaskTypes to script', async () => {
- const maxAttempts = _.random(2, 43);
- const customMaxAttempts = _.random(44, 100);
- const taskManagerId = uuidv1();
- const fieldUpdates = {
- ownerId: taskManagerId,
- retryAt: new Date(Date.now()),
- };
- const definitions = new TaskTypeDictionary(mockLogger());
- definitions.registerTaskDefinitions({
- foo: {
- title: 'foo',
- createTaskRunner: jest.fn(),
- },
- bar: {
- title: 'bar',
- maxAttempts: customMaxAttempts,
- createTaskRunner: jest.fn(),
- },
- foobar: {
- title: 'foobar',
- maxAttempts: customMaxAttempts,
- createTaskRunner: jest.fn(),
- },
- });
-
- const {
- args: {
- updateByQuery: [{ query, script }],
- },
- } = await testClaimAvailableTasks({
- storeOpts: {
- definitions,
- taskManagerId,
- },
- taskClaimingOpts: {
- maxAttempts,
- },
- claimingOpts: {
- claimOwnershipUntil: new Date(),
- },
- excludedTaskTypes: ['foobar'],
- unusedTaskTypes: ['barfoo'],
- });
- expect(query).toMatchObject({
- bool: {
- must: [
- {
- bool: {
- must: [
- {
- term: {
- 'task.enabled': true,
- },
- },
- ],
- },
- },
- {
- bool: {
- should: [
- {
- bool: {
- must: [
- { term: { 'task.status': 'idle' } },
- { range: { 'task.runAt': { lte: 'now' } } },
- ],
- },
- },
- {
- bool: {
- must: [
- {
- bool: {
- should: [
- { term: { 'task.status': 'running' } },
- { term: { 'task.status': 'claiming' } },
- ],
- },
- },
- { range: { 'task.retryAt': { lte: 'now' } } },
- ],
- },
- },
- ],
- },
- },
- ],
- filter: [
- {
- bool: {
- must_not: [
- {
- bool: {
- should: [
- { term: { 'task.status': 'running' } },
- { term: { 'task.status': 'claiming' } },
- ],
- must: { range: { 'task.retryAt': { gt: 'now' } } },
- },
- },
- ],
- },
- },
- ],
- },
- });
- expect(script).toMatchObject({
- source: expect.any(String),
- lang: 'painless',
- params: {
- fieldUpdates,
- claimableTaskTypes: ['foo', 'bar'],
- skippedTaskTypes: ['foobar'],
- unusedTaskTypes: ['barfoo'],
- taskMaxAttempts: {
- bar: customMaxAttempts,
- foo: maxAttempts,
- },
- },
- });
- });
-
test('it claims tasks by setting their ownerId, status and retryAt', async () => {
const taskManagerId = uuidv1();
const claimOwnershipUntil = new Date(Date.now());
@@ -1356,7 +1224,6 @@ if (doc['task.runAt'].size()!=0) {
strategy: 'update_by_query',
definitions,
excludedTaskTypes: [],
- unusedTypes: [],
taskStore,
maxAttempts: 2,
getAvailableCapacity,
diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.ts
index 5a4bccb43b984..fdfd09e07f9c7 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.ts
@@ -51,7 +51,6 @@ interface OwnershipClaimingOpts {
taskStore: TaskStore;
events$: Subject;
definitions: TaskTypeDictionary;
- unusedTypes: string[];
excludedTaskTypes: string[];
taskMaxAttempts: Record;
}
@@ -60,7 +59,7 @@ export async function claimAvailableTasksUpdateByQuery(
opts: TaskClaimerOpts
): Promise {
const { getCapacity, claimOwnershipUntil, batches, events$, taskStore } = opts;
- const { definitions, unusedTypes, excludedTaskTypes, taskMaxAttempts } = opts;
+ const { definitions, excludedTaskTypes, taskMaxAttempts } = opts;
const initialCapacity = getCapacity();
let accumulatedResult = getEmptyClaimOwnershipResult();
@@ -83,7 +82,6 @@ export async function claimAvailableTasksUpdateByQuery(
taskTypes: isLimited(batch) ? new Set([batch.tasksTypes]) : batch.tasksTypes,
taskStore,
definitions,
- unusedTypes,
excludedTaskTypes,
taskMaxAttempts,
});
@@ -137,7 +135,6 @@ async function markAvailableTasksAsClaimed({
claimOwnershipUntil,
size,
taskTypes,
- unusedTypes,
taskMaxAttempts,
}: OwnershipClaimingOpts): Promise {
const { taskTypesToSkip = [], taskTypesToClaim = [] } = groupBy(
@@ -164,7 +161,6 @@ async function markAvailableTasksAsClaimed({
},
claimableTaskTypes: taskTypesToClaim,
skippedTaskTypes: taskTypesToSkip,
- unusedTaskTypes: unusedTypes,
taskMaxAttempts: pick(taskMaxAttempts, taskTypesToClaim),
});
diff --git a/x-pack/plugins/task_manager/tsconfig.json b/x-pack/plugins/task_manager/tsconfig.json
index 55ad764c5bdcd..b11eaaf44a905 100644
--- a/x-pack/plugins/task_manager/tsconfig.json
+++ b/x-pack/plugins/task_manager/tsconfig.json
@@ -27,7 +27,8 @@
"@kbn/logging",
"@kbn/core-lifecycle-server",
"@kbn/cloud-plugin",
- "@kbn/core-saved-objects-base-server-internal"
+ "@kbn/core-saved-objects-base-server-internal",
+ "@kbn/core-elasticsearch-server",
],
"exclude": ["target/**/*"]
}
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 0008cac9a55dd..f03c509001511 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -862,11 +862,7 @@
"controls.controlGroup.manageControl.dataSource.dataViewTitle": "Vue de données",
"controls.controlGroup.manageControl.dataSource.fieldListErrorTitle": "Erreur lors du chargement de la liste des champs",
"controls.controlGroup.manageControl.dataSource.fieldTitle": "Champ",
- "controls.controlGroup.manageControl.dataSource.formGroupDescription": "Sélectionnez la vue de données et le champ pour lesquels vous voulez créer un contrôle.",
- "controls.controlGroup.manageControl.dataSource.formGroupTitle": "Source de données",
"controls.controlGroup.manageControl.dataSource.selectDataViewMessage": "Veuillez sélectionner une vue de données",
- "controls.controlGroup.manageControl.displaySettings.formGroupDescription": "Changez la manière dont le contrôle apparaît sur votre tableau de bord.",
- "controls.controlGroup.manageControl.displaySettings.formGroupTitle": "Paramètres d'affichage",
"controls.controlGroup.manageControl.displaySettings.growSwitchTitle": "Augmenter la largeur en fonction de l'espace disponible",
"controls.controlGroup.manageControl.displaySettings.titleInputTitle": "Étiquette",
"controls.controlGroup.manageControl.displaySettings.widthInputTitle": "Largeur minimale",
@@ -36088,32 +36084,31 @@
"xpack.searchIndices.server.createIndex.errorMessage": "Échec de la création de l'index en raison d'une exception. {errorMessage}",
"xpack.searchIndices.server.deleteDocument.errorMessage": "Impossible de supprimer le document",
"xpack.searchIndices.settingsTabLabel": "Paramètres",
- "xpack.searchIndices.startPage.codeView.apiKeyDescription": "Assurez-vous de le conserver dans un endroit sûr. Vous ne pourrez pas le récupérer plus tard.",
- "xpack.searchIndices.startPage.codeView.apiKeyTitle": "Copier votre clé d'API",
- "xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription": "Créez une clé d'API pour vous connecter à Elasticsearch.",
- "xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle": "Créer une clé d'API",
- "xpack.searchIndices.startPage.createIndex.action.text": "Créer mon index",
- "xpack.searchIndices.startPage.createIndex.apiKeyCreation.description": "Nous allons créer une clé d'API pour cet index",
- "xpack.searchIndices.startPage.createIndex.description": "Un index stocke vos données et définit le schéma, ou les mappings de champs, pour vos recherches",
- "xpack.searchIndices.startPage.createIndex.fileUpload.link": "Charger un fichier",
- "xpack.searchIndices.startPage.createIndex.fileUpload.text": "Vous disposez déjà de données ? {link}",
- "xpack.searchIndices.startPage.createIndex.name.helpText": "Les noms d'index doivent être en minuscules et ne peuvent contenir que des tirets et des chiffres",
- "xpack.searchIndices.startPage.createIndex.name.label": "Nommer votre index",
- "xpack.searchIndices.startPage.createIndex.name.placeholder": "Définir un nom pour votre index",
- "xpack.searchIndices.startPage.createIndex.permissionTooltip": "Vous ne disposez pas d'autorisation pour créer un index.",
+ "xpack.searchIndices.shared.codeView.apiKeyDescription": "Assurez-vous de le conserver dans un endroit sûr. Vous ne pourrez pas le récupérer plus tard.",
+ "xpack.searchIndices.shared.codeView.apiKeyTitle": "Copier votre clé d'API",
+ "xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription": "Créez une clé d'API pour vous connecter à Elasticsearch.",
+ "xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle": "Créer une clé d'API",
+ "xpack.searchIndices.shared.createIndex.action.text": "Créer mon index",
+ "xpack.searchIndices.shared.createIndex.apiKeyCreation.description": "Nous allons créer une clé d'API pour cet index",
+ "xpack.searchIndices.shared.createIndex.description": "Un index stocke vos données et définit le schéma, ou les mappings de champs, pour vos recherches",
+ "xpack.searchIndices.shared.createIndex.fileUpload.link": "Charger un fichier",
+ "xpack.searchIndices.shared.createIndex.fileUpload.text": "Vous disposez déjà de données ? {link}",
+ "xpack.searchIndices.shared.createIndex.name.helpText": "Les noms d'index doivent être en minuscules et ne peuvent contenir que des tirets et des chiffres",
+ "xpack.searchIndices.shared.createIndex.name.label": "Nommer votre index",
+ "xpack.searchIndices.shared.createIndex.name.placeholder": "Définir un nom pour votre index",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button": "Collectez et analysez les logs",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle": "Explorer Logstash et Beats",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button": "Démarrer un essai d'Observability",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle": "Puissant monitoring des performances",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.title": "Vous cherchez à stocker vos logs ou vos données d'indicateurs ?",
+ "xpack.searchIndices.shared.createIndex.pageTitle": "Elasticsearch",
+ "xpack.searchIndices.shared.createIndex.permissionTooltip": "Vous ne disposez pas d'autorisation pour créer un index.",
+ "xpack.searchIndices.shared.createIndex.viewSelect.code": "Code",
+ "xpack.searchIndices.shared.createIndex.viewSelect.legend": "Créer une sélection de vue d'index",
+ "xpack.searchIndices.shared.createIndex.viewSelect.ui": "Interface utilisateur",
+ "xpack.searchIndices.shared.statusFetchError.title": "Erreur lors du chargement des index",
+ "xpack.searchIndices.shared.statusFetchError.unknownError": "Erreur inconnue lors de la récupération des index.",
"xpack.searchIndices.startPage.createIndex.title": "Créer votre premier index",
- "xpack.searchIndices.startPage.createIndex.viewSelec.legend": "Créer une sélection de vue d'index",
- "xpack.searchIndices.startPage.createIndex.viewSelect.code": "Code",
- "xpack.searchIndices.startPage.createIndex.viewSelect.ui": "Interface utilisateur",
- "xpack.searchIndices.startPage.observabilityCallout.logs.button": "Collectez et analysez les logs",
- "xpack.searchIndices.startPage.observabilityCallout.logs.subTitle": "Explorer Logstash et Beats",
- "xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button": "Démarrer un essai d'Observability",
- "xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle": "Puissant monitoring des performances",
- "xpack.searchIndices.startPage.observabilityCallout.title": "Vous cherchez à stocker vos logs ou vos données d'indicateurs ?",
- "xpack.searchIndices.startPage.pageDescription": "Vectorisez, recherchez et visualisez vos données",
- "xpack.searchIndices.startPage.pageTitle": "Elasticsearch",
- "xpack.searchIndices.startPage.statusFetchError.title": "Erreur lors du chargement des index",
- "xpack.searchIndices.startPage.statusFetchError.unknownError": "Erreur inconnue lors de la récupération des index.",
"xpack.searchInferenceEndpoints.actions.copyID": "Copier l'identifiant du point de terminaison d'inférence {inferenceId}",
"xpack.searchInferenceEndpoints.actions.copyIDSuccess": "Identifiant du point de terminaison d'inférence {inferenceId} copié",
"xpack.searchInferenceEndpoints.actions.deleteEndpoint": "Supprimer le point de terminaison d'inférence {selectedEndpointName}",
@@ -43280,7 +43275,6 @@
"xpack.serverlessSearch.nav.content.indices": "Gestion des index",
"xpack.serverlessSearch.nav.devTools": "Outils de développement",
"xpack.serverlessSearch.nav.gettingStarted": "Commencer",
- "xpack.serverlessSearch.nav.home": "Accueil",
"xpack.serverlessSearch.nav.mngt": "Gestion",
"xpack.serverlessSearch.nav.performance": "Performances",
"xpack.serverlessSearch.nav.projectSettings": "Paramètres de projet",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 1d90ec5e84fd9..0b5587b2bd916 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -864,11 +864,7 @@
"controls.controlGroup.manageControl.dataSource.dataViewTitle": "データビュー",
"controls.controlGroup.manageControl.dataSource.fieldListErrorTitle": "フィールドリストの読み込みエラー",
"controls.controlGroup.manageControl.dataSource.fieldTitle": "フィールド",
- "controls.controlGroup.manageControl.dataSource.formGroupDescription": "コントロールを作成するデータビューとフィールドを選択します。",
- "controls.controlGroup.manageControl.dataSource.formGroupTitle": "データソース",
"controls.controlGroup.manageControl.dataSource.selectDataViewMessage": "データビューを選択してください",
- "controls.controlGroup.manageControl.displaySettings.formGroupDescription": "ダッシュボードにコントロールを表示する方法を変更します。",
- "controls.controlGroup.manageControl.displaySettings.formGroupTitle": "表示設定",
"controls.controlGroup.manageControl.displaySettings.growSwitchTitle": "空きスペースに合わせて幅を拡大",
"controls.controlGroup.manageControl.displaySettings.titleInputTitle": "ラベル",
"controls.controlGroup.manageControl.displaySettings.widthInputTitle": "最小幅",
@@ -36056,32 +36052,31 @@
"xpack.searchIndices.server.createIndex.errorMessage": "例外が発生したため、インデックスを作成できませんでした。{errorMessage}",
"xpack.searchIndices.server.deleteDocument.errorMessage": "ドキュメントを削除できませんでした",
"xpack.searchIndices.settingsTabLabel": "設定",
- "xpack.searchIndices.startPage.codeView.apiKeyDescription": "必ず安全に保管してください。後から取得することはできません。",
- "xpack.searchIndices.startPage.codeView.apiKeyTitle": "APIキーをコピー",
- "xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription": "Elasticsearchに接続するためのAPIキーを作成します。",
- "xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle": "APIキーを作成する",
- "xpack.searchIndices.startPage.createIndex.action.text": "インデックスを作成",
- "xpack.searchIndices.startPage.createIndex.apiKeyCreation.description": "このインデックスのAPIキーを作成します",
- "xpack.searchIndices.startPage.createIndex.description": "インデックスはデータを格納し、検索のためのスキーマ、つまりフィールドマッピングを定義します。",
- "xpack.searchIndices.startPage.createIndex.fileUpload.link": "ファイルをアップロード",
- "xpack.searchIndices.startPage.createIndex.fileUpload.text": "すでに一部のデータがありますか?{link}",
- "xpack.searchIndices.startPage.createIndex.name.helpText": "インデックス名は小文字で、ハイフンと数字のみを使用する必要があります。",
- "xpack.searchIndices.startPage.createIndex.name.label": "インデックスの名前を指定",
- "xpack.searchIndices.startPage.createIndex.name.placeholder": "インデックスの名前を入力",
- "xpack.searchIndices.startPage.createIndex.permissionTooltip": "APIキーを作成する権限がありません。",
+ "xpack.searchIndices.shared.codeView.apiKeyDescription": "必ず安全に保管してください。後から取得することはできません。",
+ "xpack.searchIndices.shared.codeView.apiKeyTitle": "APIキーをコピー",
+ "xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription": "Elasticsearchに接続するためのAPIキーを作成します。",
+ "xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle": "APIキーを作成する",
+ "xpack.searchIndices.shared.createIndex.action.text": "インデックスを作成",
+ "xpack.searchIndices.shared.createIndex.apiKeyCreation.description": "このインデックスのAPIキーを作成します",
+ "xpack.searchIndices.shared.createIndex.description": "インデックスはデータを格納し、検索のためのスキーマ、つまりフィールドマッピングを定義します。",
+ "xpack.searchIndices.shared.createIndex.fileUpload.link": "ファイルをアップロード",
+ "xpack.searchIndices.shared.createIndex.fileUpload.text": "すでに一部のデータがありますか?{link}",
+ "xpack.searchIndices.shared.createIndex.name.helpText": "インデックス名は小文字で、ハイフンと数字のみを使用する必要があります。",
+ "xpack.searchIndices.shared.createIndex.name.label": "インデックスの名前を指定",
+ "xpack.searchIndices.shared.createIndex.name.placeholder": "インデックスの名前を入力",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button": "ログを収集して分析",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle": "LogstashとBeatsを探索",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button": "オブザーバビリティの試用を開始",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle": "強力なパフォーマンス監視",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.title": "ログとメトリックデータを格納する方法をお探しですか?",
+ "xpack.searchIndices.shared.createIndex.pageTitle": "Elasticsearch",
+ "xpack.searchIndices.shared.createIndex.permissionTooltip": "APIキーを作成する権限がありません。",
+ "xpack.searchIndices.shared.createIndex.viewSelect.code": "コード",
+ "xpack.searchIndices.shared.createIndex.viewSelect.legend": "インデックスビュー選択を作成",
+ "xpack.searchIndices.shared.createIndex.viewSelect.ui": "UI",
+ "xpack.searchIndices.shared.statusFetchError.title": "インデックスの読み込み中にエラーが発生",
+ "xpack.searchIndices.shared.statusFetchError.unknownError": "インデックスの取得中の不明なエラー",
"xpack.searchIndices.startPage.createIndex.title": "最初のインデックスを作成",
- "xpack.searchIndices.startPage.createIndex.viewSelec.legend": "インデックスビュー選択を作成",
- "xpack.searchIndices.startPage.createIndex.viewSelect.code": "コード",
- "xpack.searchIndices.startPage.createIndex.viewSelect.ui": "UI",
- "xpack.searchIndices.startPage.observabilityCallout.logs.button": "ログを収集して分析",
- "xpack.searchIndices.startPage.observabilityCallout.logs.subTitle": "LogstashとBeatsを探索",
- "xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button": "オブザーバビリティの試用を開始",
- "xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle": "強力なパフォーマンス監視",
- "xpack.searchIndices.startPage.observabilityCallout.title": "ログとメトリックデータを格納する方法をお探しですか?",
- "xpack.searchIndices.startPage.pageDescription": "データをベクトル化、検索、可視化",
- "xpack.searchIndices.startPage.pageTitle": "Elasticsearch",
- "xpack.searchIndices.startPage.statusFetchError.title": "インデックスの読み込み中にエラーが発生",
- "xpack.searchIndices.startPage.statusFetchError.unknownError": "インデックスの取得中の不明なエラー",
"xpack.searchInferenceEndpoints.actions.copyID": "推論エンドポイント ID {inferenceId}をコピー",
"xpack.searchInferenceEndpoints.actions.copyIDSuccess": "推論エンドポイント ID {inferenceId}がコピーされました",
"xpack.searchInferenceEndpoints.actions.deleteEndpoint": "推論エンドポイント{selectedEndpointName}を削除",
@@ -43245,7 +43240,6 @@
"xpack.serverlessSearch.nav.content.indices": "インデックス管理",
"xpack.serverlessSearch.nav.devTools": "開発ツール",
"xpack.serverlessSearch.nav.gettingStarted": "はじめに",
- "xpack.serverlessSearch.nav.home": "ホーム",
"xpack.serverlessSearch.nav.mngt": "管理",
"xpack.serverlessSearch.nav.performance": "パフォーマンス",
"xpack.serverlessSearch.nav.projectSettings": "プロジェクト設定",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 30eb1f0c0fc2a..4d4db174396e2 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -856,11 +856,7 @@
"controls.controlGroup.manageControl.dataSource.dataViewTitle": "数据视图",
"controls.controlGroup.manageControl.dataSource.fieldListErrorTitle": "加载字段列表时出错",
"controls.controlGroup.manageControl.dataSource.fieldTitle": "字段",
- "controls.controlGroup.manageControl.dataSource.formGroupDescription": "选择要为其创建控件的数据视图和字段。",
- "controls.controlGroup.manageControl.dataSource.formGroupTitle": "数据源",
"controls.controlGroup.manageControl.dataSource.selectDataViewMessage": "请选择数据视图",
- "controls.controlGroup.manageControl.displaySettings.formGroupDescription": "更改控件在仪表板上的显示方式。",
- "controls.controlGroup.manageControl.displaySettings.formGroupTitle": "显示设置",
"controls.controlGroup.manageControl.displaySettings.growSwitchTitle": "扩大宽度以适应可用空间",
"controls.controlGroup.manageControl.displaySettings.titleInputTitle": "标签",
"controls.controlGroup.manageControl.displaySettings.widthInputTitle": "最小宽度",
@@ -35493,32 +35489,30 @@
"xpack.searchIndices.server.createIndex.errorMessage": "由于出现异常,无法创建索引。{errorMessage}",
"xpack.searchIndices.server.deleteDocument.errorMessage": "无法删除文档",
"xpack.searchIndices.settingsTabLabel": "设置",
- "xpack.searchIndices.startPage.codeView.apiKeyDescription": "请确保将其存放在某个安全位置。稍后您将无法对其进行检索。",
- "xpack.searchIndices.startPage.codeView.apiKeyTitle": "复制您的 API 密钥",
- "xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription": "创建 API 密钥以连接到 Elasticsearch。",
- "xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle": "创建 API 密钥",
- "xpack.searchIndices.startPage.createIndex.action.text": "创建我的索引",
- "xpack.searchIndices.startPage.createIndex.apiKeyCreation.description": "我们将为此索引创建 API 密钥",
- "xpack.searchIndices.startPage.createIndex.description": "索引存储您的数据并为您的搜索定义架构或字段映射",
- "xpack.searchIndices.startPage.createIndex.fileUpload.link": "上传文件",
- "xpack.searchIndices.startPage.createIndex.fileUpload.text": "已具有某些数据?{link}",
- "xpack.searchIndices.startPage.createIndex.name.helpText": "索引名称必须为小写,并且只能包含连字符和数字",
- "xpack.searchIndices.startPage.createIndex.name.label": "命名您的索引",
- "xpack.searchIndices.startPage.createIndex.name.placeholder": "输入索引的名称",
- "xpack.searchIndices.startPage.createIndex.permissionTooltip": "您无权创建索引。",
+ "xpack.searchIndices.shared.codeView.apiKeyDescription": "请确保将其存放在某个安全位置。稍后您将无法对其进行检索。",
+ "xpack.searchIndices.shared.codeView.apiKeyTitle": "复制您的 API 密钥",
+ "xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription": "创建 API 密钥以连接到 Elasticsearch。",
+ "xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle": "创建 API 密钥",
+ "xpack.searchIndices.shared.createIndex.action.text": "创建我的索引",
+ "xpack.searchIndices.shared.createIndex.apiKeyCreation.description": "我们将为此索引创建 API 密钥",
+ "xpack.searchIndices.shared.createIndex.description": "索引存储您的数据并为您的搜索定义架构或字段映射",
+ "xpack.searchIndices.shared.createIndex.fileUpload.link": "上传文件",
+ "xpack.searchIndices.shared.createIndex.fileUpload.text": "已具有某些数据?{link}",
+ "xpack.searchIndices.shared.createIndex.name.helpText": "索引名称必须为小写,并且只能包含连字符和数字",
+ "xpack.searchIndices.shared.createIndex.name.label": "命名您的索引",
+ "xpack.searchIndices.shared.createIndex.name.placeholder": "输入索引的名称",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button": "收集和分析日志",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle": "浏览 Logstash 和 Beats",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button": "开始 Observability 试用",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle": "强大的性能监测",
+ "xpack.searchIndices.shared.createIndex.observabilityCallout.title": "计划存储您的日志或指标数据?",
+ "xpack.searchIndices.shared.createIndex.pageTitle": "Elasticsearch",
+ "xpack.searchIndices.shared.createIndex.permissionTooltip": "您无权创建索引。",
+ "xpack.searchIndices.shared.createIndex.viewSelect.code": "Code",
+ "xpack.searchIndices.shared.createIndex.viewSelect.ui": "UI",
+ "xpack.searchIndices.shared.statusFetchError.title": "加载索引时出错",
+ "xpack.searchIndices.shared.statusFetchError.unknownError": "提取索引时出现未知错误。",
"xpack.searchIndices.startPage.createIndex.title": "创建您的首个索引",
- "xpack.searchIndices.startPage.createIndex.viewSelec.legend": "创建索引视图选择",
- "xpack.searchIndices.startPage.createIndex.viewSelect.code": "Code",
- "xpack.searchIndices.startPage.createIndex.viewSelect.ui": "UI",
- "xpack.searchIndices.startPage.observabilityCallout.logs.button": "收集和分析日志",
- "xpack.searchIndices.startPage.observabilityCallout.logs.subTitle": "浏览 Logstash 和 Beats",
- "xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button": "开始 Observability 试用",
- "xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle": "强大的性能监测",
- "xpack.searchIndices.startPage.observabilityCallout.title": "计划存储您的日志或指标数据?",
- "xpack.searchIndices.startPage.pageDescription": "向量化、搜索和可视化您的数据",
- "xpack.searchIndices.startPage.pageTitle": "Elasticsearch",
- "xpack.searchIndices.startPage.statusFetchError.title": "加载索引时出错",
- "xpack.searchIndices.startPage.statusFetchError.unknownError": "提取索引时出现未知错误。",
"xpack.searchInferenceEndpoints.actions.copyID": "复制推理终端 ID {inferenceId}",
"xpack.searchInferenceEndpoints.actions.copyIDSuccess": "已复制推理终端 ID {inferenceId}",
"xpack.searchInferenceEndpoints.actions.deleteEndpoint": "删除推理终端 {selectedEndpointName}",
@@ -42590,7 +42584,6 @@
"xpack.serverlessSearch.nav.content.indices": "索引管理",
"xpack.serverlessSearch.nav.devTools": "开发工具",
"xpack.serverlessSearch.nav.gettingStarted": "入门",
- "xpack.serverlessSearch.nav.home": "主页",
"xpack.serverlessSearch.nav.mngt": "管理",
"xpack.serverlessSearch.nav.performance": "性能",
"xpack.serverlessSearch.nav.projectSettings": "项目设置",
diff --git a/x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture/server/plugin.ts b/x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture/server/plugin.ts
index 6e4eba065ec70..f6e3383270436 100644
--- a/x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture/server/plugin.ts
+++ b/x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture/server/plugin.ts
@@ -95,6 +95,34 @@ export class SampleTaskManagerFixturePlugin
return res.ok({ body: {} });
}
);
+
+ router.post(
+ {
+ path: '/api/alerting_tasks/run_mark_tasks_as_unrecognized',
+ validate: {
+ body: schema.object({}),
+ },
+ },
+ async (
+ context: RequestHandlerContext,
+ req: KibanaRequest,
+ res: KibanaResponseFactory
+ ): Promise> => {
+ try {
+ const taskManager = await this.taskManagerStart;
+ await taskManager.ensureScheduled({
+ id: 'mark_removed_tasks_as_unrecognized',
+ taskType: 'task_manager:mark_removed_tasks_as_unrecognized',
+ schedule: { interval: '1h' },
+ state: {},
+ params: {},
+ });
+ return res.ok({ body: await taskManager.runSoon('mark_removed_tasks_as_unrecognized') });
+ } catch (err) {
+ return res.ok({ body: { id: 'mark_removed_tasks_as_unrecognized', error: `${err}` } });
+ }
+ }
+ );
}
public start(core: CoreStart, { taskManager }: SampleTaskManagerFixtureStartDeps) {
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts
index 0086dd2679c74..f05075be810a1 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts
@@ -138,6 +138,12 @@ export default function createScheduledTaskIdTests({ getService }: FtrProviderCo
// When we enable the rule, the unrecognized task should be removed and a new
// task created in its place
+ await supertestWithoutAuth
+ .post('/api/alerting_tasks/run_mark_tasks_as_unrecognized')
+ .set('kbn-xsrf', 'foo')
+ .send({})
+ .expect(200);
+
// scheduled task should exist and be unrecognized
await retry.try(async () => {
const taskRecordLoaded = await getScheduledTask(RULE_ID);
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/constants/archiver.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/constants/archiver.ts
new file mode 100644
index 0000000000000..6afc2e9eca63b
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/constants/archiver.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+type ArchiveName =
+ | '8.0.0'
+ | 'apm_8.0.0'
+ | 'apm_mappings_only_8.0.0'
+ | 'infra_metrics_and_apm'
+ | 'metrics_8.0.0'
+ | 'ml_8.0.0'
+ | 'observability_overview'
+ | 'rum_8.0.0'
+ | 'rum_test_data';
+
+export const ARCHIVER_ROUTES: { [key in ArchiveName]: string } = {
+ '8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0',
+ 'apm_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0',
+ 'apm_mappings_only_8.0.0':
+ 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0',
+ infra_metrics_and_apm:
+ 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm',
+ 'metrics_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0',
+ 'ml_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/ml_8.0.0',
+ observability_overview:
+ 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/observability_overview',
+ 'rum_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_8.0.0',
+ rum_test_data: 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data',
+};
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/failed_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/failed_transactions.spec.ts
new file mode 100644
index 0000000000000..549f48009197f
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/failed_transactions.spec.ts
@@ -0,0 +1,236 @@
+/*
+ * 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 { orderBy } from 'lodash';
+import expect from '@kbn/expect';
+import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
+import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
+import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
+import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
+
+// These tests go through the full sequence of queries required
+// to get the final results for a failed transactions correlation analysis.
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
+ // This matches the parameters used for the other tab's queries in `../correlations/*`.
+ const getOptions = () => ({
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ kuery: '',
+ });
+
+ describe('failed transactions', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const overallDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(overallDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${overallDistributionResponse.status}'`
+ );
+
+ const errorDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(errorDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${errorDistributionResponse.status}'`
+ );
+
+ const fieldCandidatesResponse = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
+ params: {
+ query: getOptions(),
+ },
+ });
+
+ expect(fieldCandidatesResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
+ );
+
+ const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/correlations/p_values/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
+ },
+ },
+ });
+
+ expect(failedTransactionsCorrelationsResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
+ );
+
+ const finalRawResponse: FailedTransactionsCorrelationsResponse = {
+ ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
+ percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
+ overallHistogram: overallDistributionResponse.body?.overallHistogram,
+ failedTransactionsCorrelations:
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
+ };
+
+ expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
+ 0,
+ `Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
+ );
+ });
+ });
+
+ describe('with data', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
+ it('runs queries and returns results', async () => {
+ const overallDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(overallDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${overallDistributionResponse.status}'`
+ );
+
+ const errorDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(errorDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${errorDistributionResponse.status}'`
+ );
+
+ const fieldCandidatesResponse = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
+ params: {
+ query: getOptions(),
+ },
+ });
+
+ expect(fieldCandidatesResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
+ );
+
+ const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
+ (t) => !(t === EVENT_OUTCOME)
+ );
+
+ // Identified 80 fieldCandidates.
+ expect(fieldCandidates.length).to.eql(
+ 80,
+ `Expected field candidates length to be '80', got '${fieldCandidates.length}'`
+ );
+
+ const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/correlations/p_values/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ fieldCandidates,
+ },
+ },
+ });
+
+ expect(failedTransactionsCorrelationsResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
+ );
+
+ const fieldsToSample = new Set();
+ if (
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0
+ ) {
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach(
+ (d) => {
+ fieldsToSample.add(d.fieldName);
+ }
+ );
+ }
+
+ const finalRawResponse: FailedTransactionsCorrelationsResponse = {
+ ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
+ percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
+ overallHistogram: overallDistributionResponse.body?.overallHistogram,
+ errorHistogram: errorDistributionResponse.body?.overallHistogram,
+ failedTransactionsCorrelations:
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
+ };
+
+ expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
+ expect(finalRawResponse?.errorHistogram?.length).to.be(101);
+ expect(finalRawResponse?.overallHistogram?.length).to.be(101);
+
+ expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
+ 29,
+ `Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
+ );
+
+ const sortedCorrelations = orderBy(
+ finalRawResponse?.failedTransactionsCorrelations,
+ ['score', 'fieldName', 'fieldValue'],
+ ['desc', 'asc', 'asc']
+ );
+ const correlation = sortedCorrelations?.[0];
+
+ expect(typeof correlation).to.be('object');
+ expect(correlation?.doc_count).to.be(31);
+ expect(correlation?.score).to.be(83.70467673605746);
+ expect(correlation?.bg_count).to.be(31);
+ expect(correlation?.fieldName).to.be('transaction.result');
+ expect(correlation?.fieldValue).to.be('HTTP 5xx');
+ expect(typeof correlation?.pValue).to.be('number');
+ expect(typeof correlation?.normalizedScore).to.be('number');
+ expect(typeof correlation?.failurePercentage).to.be('number');
+ expect(typeof correlation?.successPercentage).to.be('number');
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_candidates.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_candidates.spec.ts
new file mode 100644
index 0000000000000..8db9a7df05fd3
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_candidates.spec.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
+
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
+
+ const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';
+
+ const getOptions = () => ({
+ params: {
+ query: {
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ kuery: '',
+ },
+ },
+ });
+
+ describe('field candidates', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
+
+ expect(response.status).to.be(200);
+ // If the source indices are empty, there will be no field candidates
+ // because of the `include_empty_fields: false` option in the query.
+ expect(response.body?.fieldCandidates.length).to.be(0);
+ });
+ });
+
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
+ it('returns field candidates', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
+
+ expect(response.status).to.eql(200);
+ expect(response.body?.fieldCandidates.length).to.be(81);
+ });
+ });
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/field_value_pairs.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_value_pairs.spec.ts
similarity index 59%
rename from x-pack/test/apm_api_integration/tests/correlations/field_value_pairs.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_value_pairs.spec.ts
index 4765e83342e52..9fcd438421b6a 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/field_value_pairs.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_value_pairs.spec.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/field_value_pairs/transactions';
@@ -41,22 +42,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
- registry.when('field value pairs without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
+ describe('field value pairs', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
- expect(response.status).to.be(200);
- expect(response.body?.fieldValuePairs.length).to.be(0);
+ expect(response.status).to.be(200);
+ expect(response.body?.fieldValuePairs.length).to.be(0);
+ });
});
- });
- registry.when(
- 'field value pairs with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
it('returns field value pairs', async () => {
const response = await apmApiClient.readUser({
endpoint,
@@ -66,6 +72,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.fieldValuePairs.length).to.be(124);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts
new file mode 100644
index 0000000000000..660556edb8d79
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
+ describe('correlations', () => {
+ loadTestFile(require.resolve('./failed_transactions.spec.ts'));
+ loadTestFile(require.resolve('./field_candidates.spec.ts'));
+ loadTestFile(require.resolve('./field_value_pairs.spec.ts'));
+ loadTestFile(require.resolve('./latency.spec.ts'));
+ loadTestFile(require.resolve('./p_values.spec.ts'));
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/latency.spec.ts
similarity index 93%
rename from x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/latency.spec.ts
index 5326136976428..e0080806f6a0e 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/latency.spec.ts
@@ -14,13 +14,14 @@ import type {
LatencyCorrelationsResponse,
} from '@kbn/apm-plugin/common/correlations/latency_correlations/types';
import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
// These tests go through the full sequence of queries required
// to get the final results for a latency correlation analysis.
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
// This matches the parameters used for the other tab's queries in `../correlations/*`.
const getOptions = () => ({
@@ -30,10 +31,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
kuery: '',
});
- registry.when(
- 'correlations latency overall without data',
- { config: 'trial', archives: [] },
- () => {
+ describe('latency', () => {
+ describe('overall without data', () => {
it('handles the empty state', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
@@ -104,13 +103,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(finalRawResponse?.overallHistogram).to.be(undefined);
expect(finalRawResponse?.latencyCorrelations?.length).to.be(0);
});
- }
- );
+ });
+
+ describe('with data and opbeans-node args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
- registry.when(
- 'correlations latency with data and opbeans-node args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
// putting this into a single `it` because the responses depend on each other
it('runs queries and returns results', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
@@ -250,6 +252,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(correlation?.ksTest).to.be(1.9848961005439386e-12);
expect(correlation?.histogram?.length).to.be(101);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/p_values.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/p_values.spec.ts
similarity index 58%
rename from x-pack/test/apm_api_integration/tests/correlations/p_values.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/p_values.spec.ts
index 42a9fdadbb480..ba6e3384cec93 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/p_values.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/p_values.spec.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/p_values/transactions';
@@ -41,22 +42,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
- registry.when('p values without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
+ describe('p values', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
- expect(response.status).to.be(200);
- expect(response.body?.failedTransactionsCorrelations.length).to.be(0);
+ expect(response.status).to.be(200);
+ expect(response.body?.failedTransactionsCorrelations.length).to.be(0);
+ });
});
- });
- registry.when(
- 'p values with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
it('returns p values', async () => {
const response = await apmApiClient.readUser({
endpoint,
@@ -66,6 +72,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.failedTransactionsCorrelations.length).to.be(15);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/significant_correlations.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/significant_correlations.spec.ts
similarity index 71%
rename from x-pack/test/apm_api_integration/tests/correlations/significant_correlations.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/significant_correlations.spec.ts
index d4450c192a029..e1f968d178868 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/significant_correlations.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/significant_correlations.spec.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/significant_correlations/transactions';
@@ -65,22 +66,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
- registry.when('significant correlations without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
+ describe('significant correlations', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
- expect(response.status).to.be(200);
- expect(response.body?.latencyCorrelations.length).to.be(0);
+ expect(response.status).to.be(200);
+ expect(response.body?.latencyCorrelations.length).to.be(0);
+ });
});
- });
- registry.when(
- 'significant correlations with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
it('returns significant correlations', async () => {
const response = await apmApiClient.readUser({
endpoint,
@@ -90,6 +96,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.latencyCorrelations.length).to.be(7);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts
new file mode 100644
index 0000000000000..d7a36e3e447b7
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
+ describe('entities', () => {
+ loadTestFile(require.resolve('./service_logs_error_rate_timeseries.spec.ts'));
+ loadTestFile(require.resolve('./service_logs_rate_timeseries.spec.ts'));
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_error_rate_timeseries.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_error_rate_timeseries.spec.ts
similarity index 85%
rename from x-pack/test/apm_api_integration/tests/entities/logs/service_logs_error_rate_timeseries.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_error_rate_timeseries.spec.ts
index 282039b8957c9..f6e167db0318e 100644
--- a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_error_rate_timeseries.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_error_rate_timeseries.spec.ts
@@ -9,12 +9,12 @@ import { log, timerange } from '@kbn/apm-synthtrace-client';
import { first, last } from 'lodash';
import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
-import { FtrProviderContext } from '../../../common/ftr_provider_context';
+import { LogsSynthtraceEsClient } from '@kbn/apm-synthtrace';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const registry = getService('registry');
- const apmApiClient = getService('apmApiClient');
- const logSynthtrace = getService('logSynthtraceEsClient');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const synthtrace = getService('synthtrace');
const serviceName = 'synth-go';
const start = new Date('2024-01-01T00:00:00.000Z').getTime();
@@ -45,25 +45,26 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
return response;
}
+ describe('logs error rate timeseries', () => {
+ describe('when data is not loaded', () => {
+ it('handles empty state', async () => {
+ const response = await getLogsErrorRateTimeseries();
+ expect(response.status).to.be(200);
+ expect(response.body.currentPeriod).to.empty();
+ });
+ });
- registry.when(
- 'Logs error rate timeseries when data is not loaded',
- { config: 'basic', archives: [] },
- () => {
- describe('Logs error rate api', () => {
- it('handles the empty state', async () => {
- const response = await getLogsErrorRateTimeseries();
- expect(response.status).to.be(200);
- expect(response.body.currentPeriod).to.empty();
- });
+ describe('when data loaded', () => {
+ let logSynthtrace: LogsSynthtraceEsClient;
+
+ before(async () => {
+ logSynthtrace = await synthtrace.createLogsSynthtraceEsClient();
+ });
+
+ after(async () => {
+ await logSynthtrace.clean();
});
- }
- );
- registry.when(
- 'Logs error rate timeseries when data loaded',
- { config: 'basic', archives: [] },
- () => {
describe('Logs without log level field', () => {
before(async () => {
return logSynthtrace.index([
@@ -170,6 +171,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts
new file mode 100644
index 0000000000000..fb10925b9906d
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts
@@ -0,0 +1,180 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { log, timerange } from '@kbn/apm-synthtrace-client';
+import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
+import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
+import { first, last } from 'lodash';
+import { LogsSynthtraceEsClient } from '@kbn/apm-synthtrace';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const synthtrace = getService('synthtrace');
+
+ const serviceName = 'synth-go';
+ const start = new Date('2024-01-01T00:00:00.000Z').getTime();
+ const end = new Date('2024-01-01T00:15:00.000Z').getTime() - 1;
+
+ const hostName = 'synth-host';
+
+ async function getLogsRateTimeseries(
+ overrides?: RecursivePartial<
+ APIClientRequestParamsOf<'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries'>['params']
+ >
+ ) {
+ const response = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries',
+ params: {
+ path: {
+ serviceName: 'synth-go',
+ ...overrides?.path,
+ },
+ query: {
+ start: new Date(start).toISOString(),
+ end: new Date(end).toISOString(),
+ environment: 'ENVIRONMENT_ALL',
+ kuery: '',
+ ...overrides?.query,
+ },
+ },
+ });
+ return response;
+ }
+ describe('logs rate timeseries', () => {
+ describe('when data is not loaded', () => {
+ it('handles empty state', async () => {
+ const response = await getLogsRateTimeseries();
+ expect(response.status).to.be(200);
+ expect(response.body.currentPeriod).to.empty();
+ });
+ });
+
+ describe('when data loaded', () => {
+ let logSynthtrace: LogsSynthtraceEsClient;
+
+ before(async () => {
+ logSynthtrace = await synthtrace.createLogsSynthtraceEsClient();
+ });
+
+ after(async () => {
+ await logSynthtrace.clean();
+ });
+
+ describe('Logs without log level field', () => {
+ before(async () => {
+ return logSynthtrace.index([
+ timerange(start, end)
+ .interval('1m')
+ .rate(1)
+ .generator((timestamp) =>
+ log.create().message('This is a log message').timestamp(timestamp).defaults({
+ 'log.file.path': '/my-service.log',
+ 'service.name': serviceName,
+ 'host.name': hostName,
+ })
+ ),
+ ]);
+ });
+ after(async () => {
+ await logSynthtrace.clean();
+ });
+
+ it('returns {} if log level is not available ', async () => {
+ const response = await getLogsRateTimeseries();
+ expect(response.status).to.be(200);
+ });
+ });
+
+ describe('Logs with log.level=error', () => {
+ before(async () => {
+ return logSynthtrace.index([
+ timerange(start, end)
+ .interval('1m')
+ .rate(1)
+ .generator((timestamp) =>
+ log
+ .create()
+ .message('This is a log message')
+ .logLevel('error')
+ .timestamp(timestamp)
+ .defaults({
+ 'log.file.path': '/my-service.log',
+ 'service.name': serviceName,
+ 'host.name': hostName,
+ 'service.environment': 'test',
+ })
+ ),
+ timerange(start, end)
+ .interval('2m')
+ .rate(1)
+ .generator((timestamp) =>
+ log
+ .create()
+ .message('This is an error log message')
+ .logLevel('error')
+ .timestamp(timestamp)
+ .defaults({
+ 'log.file.path': '/my-service.log',
+ 'service.name': 'my-service',
+ 'host.name': hostName,
+ 'service.environment': 'production',
+ })
+ ),
+ timerange(start, end)
+ .interval('5m')
+ .rate(1)
+ .generator((timestamp) =>
+ log
+ .create()
+ .message('This is an info message')
+ .logLevel('info')
+ .timestamp(timestamp)
+ .defaults({
+ 'log.file.path': '/my-service.log',
+ 'service.name': 'my-service',
+ 'host.name': hostName,
+ 'service.environment': 'production',
+ })
+ ),
+ ]);
+ });
+ after(async () => {
+ await logSynthtrace.clean();
+ });
+
+ it('returns log rate timeseries', async () => {
+ const response = await getLogsRateTimeseries();
+ expect(response.status).to.be(200);
+ expect(
+ response.body.currentPeriod[serviceName].every(({ y }) => y === 0.06666666666666667)
+ ).to.be(true);
+ });
+
+ it('handles environment filter', async () => {
+ const response = await getLogsRateTimeseries({ query: { environment: 'foo' } });
+ expect(response.status).to.be(200);
+ expect(response.body.currentPeriod).to.empty();
+ });
+
+ describe('when my-service is selected', () => {
+ it('returns some data', async () => {
+ const response = await getLogsRateTimeseries({
+ path: { serviceName: 'my-service' },
+ });
+
+ expect(response.status).to.be(200);
+ expect(first(response.body.currentPeriod?.['my-service'])?.y).to.be(
+ 0.18181818181818182
+ );
+ expect(last(response.body.currentPeriod?.['my-service'])?.y).to.be(0.09090909090909091);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts
index 3e490a621d3f9..f1e8fc381a072 100644
--- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts
@@ -15,6 +15,8 @@ export default function apmApiIntegrationTests({
loadTestFile(require.resolve('./mobile'));
loadTestFile(require.resolve('./custom_dashboards'));
loadTestFile(require.resolve('./dependencies'));
+ loadTestFile(require.resolve('./correlations'));
+ loadTestFile(require.resolve('./entities'));
loadTestFile(require.resolve('./cold_start'));
});
}
diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json
index a64e037343bb3..c79a4c6b52309 100644
--- a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json
+++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json
@@ -3786,10 +3786,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-error"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -4243,8 +4239,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -8183,10 +8178,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-metric"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -8640,8 +8631,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -12871,8 +12861,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -16653,10 +16642,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-profile"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -17110,8 +17095,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -20899,10 +20883,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-span"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -21356,8 +21336,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -25242,10 +25221,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-transaction"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -25699,8 +25674,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts
deleted file mode 100644
index 13754f6c7eb5a..0000000000000
--- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts
+++ /dev/null
@@ -1,223 +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 { orderBy } from 'lodash';
-import expect from '@kbn/expect';
-import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
-import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
-import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
-import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
-
-// These tests go through the full sequence of queries required
-// to get the final results for a failed transactions correlation analysis.
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
-
- // This matches the parameters used for the other tab's queries in `../correlations/*`.
- const getOptions = () => ({
- environment: 'ENVIRONMENT_ALL',
- start: '2020',
- end: '2021',
- kuery: '',
- });
-
- registry.when('failed transactions without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const overallDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(overallDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${overallDistributionResponse.status}'`
- );
-
- const errorDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(errorDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${errorDistributionResponse.status}'`
- );
-
- const fieldCandidatesResponse = await apmApiClient.readUser({
- endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
- params: {
- query: getOptions(),
- },
- });
-
- expect(fieldCandidatesResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
- );
-
- const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/correlations/p_values/transactions',
- params: {
- body: {
- ...getOptions(),
- fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
- },
- },
- });
-
- expect(failedTransactionsCorrelationsResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
- );
-
- const finalRawResponse: FailedTransactionsCorrelationsResponse = {
- ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
- percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
- overallHistogram: overallDistributionResponse.body?.overallHistogram,
- failedTransactionsCorrelations:
- failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
- };
-
- expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
- 0,
- `Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
- );
- });
- });
-
- registry.when('failed transactions with data', { config: 'trial', archives: ['8.0.0'] }, () => {
- it('runs queries and returns results', async () => {
- const overallDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(overallDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${overallDistributionResponse.status}'`
- );
-
- const errorDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(errorDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${errorDistributionResponse.status}'`
- );
-
- const fieldCandidatesResponse = await apmApiClient.readUser({
- endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
- params: {
- query: getOptions(),
- },
- });
-
- expect(fieldCandidatesResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
- );
-
- const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
- (t) => !(t === EVENT_OUTCOME)
- );
-
- // Identified 80 fieldCandidates.
- expect(fieldCandidates.length).to.eql(
- 80,
- `Expected field candidates length to be '80', got '${fieldCandidates.length}'`
- );
-
- const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/correlations/p_values/transactions',
- params: {
- body: {
- ...getOptions(),
- fieldCandidates,
- },
- },
- });
-
- expect(failedTransactionsCorrelationsResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
- );
-
- const fieldsToSample = new Set();
- if (failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0) {
- failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach((d) => {
- fieldsToSample.add(d.fieldName);
- });
- }
-
- const finalRawResponse: FailedTransactionsCorrelationsResponse = {
- ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
- percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
- overallHistogram: overallDistributionResponse.body?.overallHistogram,
- errorHistogram: errorDistributionResponse.body?.overallHistogram,
- failedTransactionsCorrelations:
- failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
- };
-
- expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
- expect(finalRawResponse?.errorHistogram?.length).to.be(101);
- expect(finalRawResponse?.overallHistogram?.length).to.be(101);
-
- expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
- 29,
- `Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
- );
-
- const sortedCorrelations = orderBy(
- finalRawResponse?.failedTransactionsCorrelations,
- ['score', 'fieldName', 'fieldValue'],
- ['desc', 'asc', 'asc']
- );
- const correlation = sortedCorrelations?.[0];
-
- expect(typeof correlation).to.be('object');
- expect(correlation?.doc_count).to.be(31);
- expect(correlation?.score).to.be(83.70467673605746);
- expect(correlation?.bg_count).to.be(31);
- expect(correlation?.fieldName).to.be('transaction.result');
- expect(correlation?.fieldValue).to.be('HTTP 5xx');
- expect(typeof correlation?.pValue).to.be('number');
- expect(typeof correlation?.normalizedScore).to.be('number');
- expect(typeof correlation?.failurePercentage).to.be('number');
- expect(typeof correlation?.successPercentage).to.be('number');
- });
- });
-}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts
deleted file mode 100644
index 4a5472cf5cb23..0000000000000
--- a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts
+++ /dev/null
@@ -1,57 +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 expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
-
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
-
- const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';
-
- const getOptions = () => ({
- params: {
- query: {
- environment: 'ENVIRONMENT_ALL',
- start: '2020',
- end: '2021',
- kuery: '',
- },
- },
- });
-
- registry.when('field candidates without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
-
- expect(response.status).to.be(200);
- // If the source indices are empty, there will be no field candidates
- // because of the `include_empty_fields: false` option in the query.
- expect(response.body?.fieldCandidates.length).to.be(0);
- });
- });
-
- registry.when(
- 'field candidates with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
- it('returns field candidates', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
-
- expect(response.status).to.eql(200);
- expect(response.body?.fieldCandidates.length).to.be(81);
- });
- }
- );
-}
diff --git a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts
deleted file mode 100644
index d4717b25bba93..0000000000000
--- a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts
+++ /dev/null
@@ -1,173 +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 expect from '@kbn/expect';
-import { log, timerange } from '@kbn/apm-synthtrace-client';
-import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
-import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
-import { first, last } from 'lodash';
-import { FtrProviderContext } from '../../../common/ftr_provider_context';
-
-export default function ApiTest({ getService }: FtrProviderContext) {
- const registry = getService('registry');
- const apmApiClient = getService('apmApiClient');
- const logSynthtrace = getService('logSynthtraceEsClient');
-
- const serviceName = 'synth-go';
- const start = new Date('2024-01-01T00:00:00.000Z').getTime();
- const end = new Date('2024-01-01T00:15:00.000Z').getTime() - 1;
-
- const hostName = 'synth-host';
-
- async function getLogsRateTimeseries(
- overrides?: RecursivePartial<
- APIClientRequestParamsOf<'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries'>['params']
- >
- ) {
- const response = await apmApiClient.readUser({
- endpoint: 'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries',
- params: {
- path: {
- serviceName: 'synth-go',
- ...overrides?.path,
- },
- query: {
- start: new Date(start).toISOString(),
- end: new Date(end).toISOString(),
- environment: 'ENVIRONMENT_ALL',
- kuery: '',
- ...overrides?.query,
- },
- },
- });
- return response;
- }
-
- registry.when(
- 'Logs rate timeseries when data is not loaded',
- { config: 'basic', archives: [] },
- () => {
- describe('Logs rate api', () => {
- it('handles the empty state', async () => {
- const response = await getLogsRateTimeseries();
- expect(response.status).to.be(200);
- expect(response.body.currentPeriod).to.empty();
- });
- });
- }
- );
-
- registry.when('Logs rate timeseries when data loaded', { config: 'basic', archives: [] }, () => {
- describe('Logs without log level field', () => {
- before(async () => {
- return logSynthtrace.index([
- timerange(start, end)
- .interval('1m')
- .rate(1)
- .generator((timestamp) =>
- log.create().message('This is a log message').timestamp(timestamp).defaults({
- 'log.file.path': '/my-service.log',
- 'service.name': serviceName,
- 'host.name': hostName,
- })
- ),
- ]);
- });
- after(async () => {
- await logSynthtrace.clean();
- });
-
- it('returns {} if log level is not available ', async () => {
- const response = await getLogsRateTimeseries();
- expect(response.status).to.be(200);
- });
- });
-
- describe('Logs with log.level=error', () => {
- before(async () => {
- return logSynthtrace.index([
- timerange(start, end)
- .interval('1m')
- .rate(1)
- .generator((timestamp) =>
- log
- .create()
- .message('This is a log message')
- .logLevel('error')
- .timestamp(timestamp)
- .defaults({
- 'log.file.path': '/my-service.log',
- 'service.name': serviceName,
- 'host.name': hostName,
- 'service.environment': 'test',
- })
- ),
- timerange(start, end)
- .interval('2m')
- .rate(1)
- .generator((timestamp) =>
- log
- .create()
- .message('This is an error log message')
- .logLevel('error')
- .timestamp(timestamp)
- .defaults({
- 'log.file.path': '/my-service.log',
- 'service.name': 'my-service',
- 'host.name': hostName,
- 'service.environment': 'production',
- })
- ),
- timerange(start, end)
- .interval('5m')
- .rate(1)
- .generator((timestamp) =>
- log
- .create()
- .message('This is an info message')
- .logLevel('info')
- .timestamp(timestamp)
- .defaults({
- 'log.file.path': '/my-service.log',
- 'service.name': 'my-service',
- 'host.name': hostName,
- 'service.environment': 'production',
- })
- ),
- ]);
- });
- after(async () => {
- await logSynthtrace.clean();
- });
-
- it('returns log rate timeseries', async () => {
- const response = await getLogsRateTimeseries();
- expect(response.status).to.be(200);
- expect(
- response.body.currentPeriod[serviceName].every(({ y }) => y === 0.06666666666666667)
- ).to.be(true);
- });
-
- it('handles environment filter', async () => {
- const response = await getLogsRateTimeseries({ query: { environment: 'foo' } });
- expect(response.status).to.be(200);
- expect(response.body.currentPeriod).to.empty();
- });
-
- describe('when my-service is selected', () => {
- it('returns some data', async () => {
- const response = await getLogsRateTimeseries({
- path: { serviceName: 'my-service' },
- });
-
- expect(response.status).to.be(200);
- expect(first(response.body.currentPeriod?.['my-service'])?.y).to.be(0.18181818181818182);
- expect(last(response.body.currentPeriod?.['my-service'])?.y).to.be(0.09090909090909091);
- });
- });
- });
- });
-}
diff --git a/x-pack/test/cloud_security_posture_api/es_archives/logs_gcp_audit/data.json b/x-pack/test/cloud_security_posture_api/es_archives/logs_gcp_audit/data.json
index 9f536d0bb6dc9..37f7ebdff5fb1 100644
--- a/x-pack/test/cloud_security_posture_api/es_archives/logs_gcp_audit/data.json
+++ b/x-pack/test/cloud_security_posture_api/es_archives/logs_gcp_audit/data.json
@@ -497,3 +497,123 @@
}
}
}
+
+{
+ "type": "doc",
+ "value": {
+ "data_stream": "logs-gcp.audit-default",
+ "id": "5",
+ "index": ".ds-logs-gcp.audit-default-2024.10.07-000001",
+ "source": {
+ "@timestamp": "2024-09-01T12:34:56.789Z",
+ "actor": {
+ "entity": {
+ "id": "admin5@example.com"
+ }
+ },
+ "client": {
+ "user": {
+ "email": "admin5@example.com"
+ }
+ },
+ "cloud": {
+ "project": {
+ "id": "your-project-id"
+ },
+ "provider": "gcp"
+ },
+ "ecs": {
+ "version": "8.11.0"
+ },
+ "event": {
+ "action": "google.iam.admin.v1.ListRoles",
+ "agent_id_status": "missing",
+ "category": [
+ "session",
+ "network",
+ "configuration"
+ ],
+ "id": "without target",
+ "ingested": "2024-10-07T17:47:35Z",
+ "kind": "event",
+ "outcome": "success",
+ "provider": "activity",
+ "type": [
+ "end",
+ "access",
+ "allowed"
+ ]
+ },
+ "gcp": {
+ "audit": {
+ "authorization_info": [
+ {
+ "granted": true,
+ "permission": "iam.roles.create",
+ "resource": "projects/your-project-id"
+ }
+ ],
+ "logentry_operation": {
+ "id": "operation-0987654321"
+ },
+ "request": {
+ "@type": "type.googleapis.com/google.iam.admin.v1.CreateRoleRequest",
+ "parent": "projects/your-project-id",
+ "role": {
+ "description": "A custom role with specific permissions",
+ "includedPermissions": [
+ "resourcemanager.projects.get",
+ "resourcemanager.projects.list"
+ ],
+ "name": "projects/your-project-id/roles/customRole",
+ "title": "Custom Role"
+ },
+ "roleId": "customRole"
+ },
+ "resource_name": "projects/your-project-id/roles/customRole",
+ "response": {
+ "@type": "type.googleapis.com/google.iam.admin.v1.Role",
+ "description": "A custom role with specific permissions",
+ "includedPermissions": [
+ "resourcemanager.projects.get",
+ "resourcemanager.projects.list"
+ ],
+ "name": "projects/your-project-id/roles/customRole",
+ "stage": "GA",
+ "title": "Custom Role"
+ },
+ "type": "type.googleapis.com/google.cloud.audit.AuditLog"
+ }
+ },
+ "log": {
+ "level": "NOTICE",
+ "logger": "projects/your-project-id/logs/cloudaudit.googleapis.com%2Factivity"
+ },
+ "related": {
+ "ip": [
+ "10.0.0.1"
+ ],
+ "user": [
+ "admin3@example.com"
+ ]
+ },
+ "service": {
+ "name": "iam.googleapis.com"
+ },
+ "source": {
+ "ip": "10.0.0.1"
+ },
+ "tags": [
+ "_geoip_database_unavailable_GeoLite2-City.mmdb",
+ "_geoip_database_unavailable_GeoLite2-ASN.mmdb"
+ ],
+ "user_agent": {
+ "device": {
+ "name": "Other"
+ },
+ "name": "Other",
+ "original": "google-cloud-sdk/324.0.0"
+ }
+ }
+ }
+}
diff --git a/x-pack/test/cloud_security_posture_api/routes/graph.ts b/x-pack/test/cloud_security_posture_api/routes/graph.ts
index bd2f71ef3b9b2..8043e6e22feb6 100644
--- a/x-pack/test/cloud_security_posture_api/routes/graph.ts
+++ b/x-pack/test/cloud_security_posture_api/routes/graph.ts
@@ -11,6 +11,8 @@ import {
} from '@kbn/core-http-common';
import expect from '@kbn/expect';
import type { Agent } from 'supertest';
+import { ApiMessageCode } from '@kbn/cloud-security-posture-common/types/graph/latest';
+import type { GraphRequest } from '@kbn/cloud-security-posture-common/types/graph/latest';
import { FtrProviderContext } from '../ftr_provider_context';
import { result } from '../utils';
import { CspSecurityCommonProvider } from './helper/user_roles_utilites';
@@ -19,12 +21,13 @@ import { CspSecurityCommonProvider } from './helper/user_roles_utilites';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
+ const logger = getService('log');
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const cspSecurity = CspSecurityCommonProvider(providerContext);
- const postGraph = (agent: Agent, body: any, auth?: { user: string; pass: string }) => {
+ const postGraph = (agent: Agent, body: GraphRequest, auth?: { user: string; pass: string }) => {
const req = agent
.post('/internal/cloud_security_posture/graph')
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
@@ -45,7 +48,6 @@ export default function (providerContext: FtrProviderContext) {
supertestWithoutAuth,
{
query: {
- actorIds: [],
eventIds: [],
start: 'now-1d/d',
end: 'now/d',
@@ -55,19 +57,7 @@ export default function (providerContext: FtrProviderContext) {
user: 'role_security_no_read_user',
pass: cspSecurity.getPasswordForUser('role_security_no_read_user'),
}
- ).expect(result(403));
- });
- });
-
- describe('Validation', () => {
- it('should return 400 when missing `actorIds` field', async () => {
- await postGraph(supertest, {
- query: {
- eventIds: [],
- start: 'now-1d/d',
- end: 'now/d',
- },
- }).expect(result(400));
+ ).expect(result(403, logger));
});
});
@@ -84,10 +74,54 @@ export default function (providerContext: FtrProviderContext) {
);
});
- it('should return an empty graph', async () => {
+ describe('Validation', () => {
+ it('should return 400 when missing `eventIds` field', async () => {
+ await postGraph(supertest, {
+ // @ts-expect-error ignore error for testing
+ query: {
+ start: 'now-1d/d',
+ end: 'now/d',
+ },
+ }).expect(result(400, logger));
+ });
+
+ it('should return 400 when missing `esQuery` field is not of type bool', async () => {
+ await postGraph(supertest, {
+ query: {
+ eventIds: [],
+ start: 'now-1d/d',
+ end: 'now/d',
+ esQuery: {
+ // @ts-expect-error ignore error for testing
+ match_all: {},
+ },
+ },
+ }).expect(result(400, logger));
+ });
+
+ it('should return 400 with unsupported `esQuery`', async () => {
+ await postGraph(supertest, {
+ query: {
+ eventIds: [],
+ start: 'now-1d/d',
+ end: 'now/d',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ unsupported: 'unsupported',
+ },
+ ],
+ },
+ },
+ },
+ }).expect(result(400, logger));
+ });
+ });
+
+ it('should return an empty graph / should return 200 when missing `esQuery` field', async () => {
const response = await postGraph(supertest, {
query: {
- actorIds: [],
eventIds: [],
start: 'now-1d/d',
end: 'now/d',
@@ -96,20 +130,32 @@ export default function (providerContext: FtrProviderContext) {
expect(response.body).to.have.property('nodes').length(0);
expect(response.body).to.have.property('edges').length(0);
+ expect(response.body).not.to.have.property('messages');
});
it('should return a graph with nodes and edges by actor', async () => {
const response = await postGraph(supertest, {
query: {
- actorIds: ['admin@example.com'],
eventIds: [],
start: '2024-09-01T00:00:00Z',
end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ match_phrase: {
+ 'actor.entity.id': 'admin@example.com',
+ },
+ },
+ ],
+ },
+ },
},
}).expect(result(200));
expect(response.body).to.have.property('nodes').length(3);
expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).not.to.have.property('messages');
response.body.nodes.forEach((node: any) => {
expect(node).to.have.property('color');
@@ -131,7 +177,6 @@ export default function (providerContext: FtrProviderContext) {
it('should return a graph with nodes and edges by alert', async () => {
const response = await postGraph(supertest, {
query: {
- actorIds: [],
eventIds: ['kabcd1234efgh5678'],
start: '2024-09-01T00:00:00Z',
end: '2024-09-02T00:00:00Z',
@@ -140,6 +185,7 @@ export default function (providerContext: FtrProviderContext) {
expect(response.body).to.have.property('nodes').length(3);
expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).not.to.have.property('messages');
response.body.nodes.forEach((node: any) => {
expect(node).to.have.property('color');
@@ -161,7 +207,6 @@ export default function (providerContext: FtrProviderContext) {
it('color of alert of failed event should be danger', async () => {
const response = await postGraph(supertest, {
query: {
- actorIds: [],
eventIds: ['failed-event'],
start: '2024-09-01T00:00:00Z',
end: '2024-09-02T00:00:00Z',
@@ -170,6 +215,7 @@ export default function (providerContext: FtrProviderContext) {
expect(response.body).to.have.property('nodes').length(3);
expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).not.to.have.property('messages');
response.body.nodes.forEach((node: any) => {
expect(node).to.have.property('color');
@@ -191,15 +237,26 @@ export default function (providerContext: FtrProviderContext) {
it('color of event of failed event should be warning', async () => {
const response = await postGraph(supertest, {
query: {
- actorIds: ['admin2@example.com'],
eventIds: [],
start: '2024-09-01T00:00:00Z',
end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ match_phrase: {
+ 'actor.entity.id': 'admin2@example.com',
+ },
+ },
+ ],
+ },
+ },
},
}).expect(result(200));
expect(response.body).to.have.property('nodes').length(3);
expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).not.to.have.property('messages');
response.body.nodes.forEach((node: any) => {
expect(node).to.have.property('color');
@@ -219,18 +276,29 @@ export default function (providerContext: FtrProviderContext) {
});
});
- it('2 grouped of events, 1 failed, 1 success', async () => {
+ it('2 grouped events, 1 failed, 1 success', async () => {
const response = await postGraph(supertest, {
query: {
- actorIds: ['admin3@example.com'],
eventIds: [],
start: '2024-09-01T00:00:00Z',
end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ match_phrase: {
+ 'actor.entity.id': 'admin3@example.com',
+ },
+ },
+ ],
+ },
+ },
},
}).expect(result(200));
expect(response.body).to.have.property('nodes').length(5);
expect(response.body).to.have.property('edges').length(6);
+ expect(response.body).not.to.have.property('messages');
expect(response.body.nodes[0].shape).equal('group', 'Groups should be the first nodes');
@@ -247,11 +315,167 @@ export default function (providerContext: FtrProviderContext) {
response.body.edges.forEach((edge: any) => {
expect(edge).to.have.property('color');
expect(edge.color).equal(
- edge.id.includes('outcome(failed)') ? 'warning' : 'primary',
+ edge.id.includes('outcome(failed)') ||
+ (edge.id.includes('grp(') && !edge.id.includes('outcome(success)'))
+ ? 'warning'
+ : 'primary',
+ `edge color mismatched [edge: ${edge.id}] [actual: ${edge.color}]`
+ );
+ });
+ });
+
+ it('should support more than 1 eventIds', async () => {
+ const response = await postGraph(supertest, {
+ query: {
+ eventIds: ['kabcd1234efgh5678', 'failed-event'],
+ start: '2024-09-01T00:00:00Z',
+ end: '2024-09-02T00:00:00Z',
+ },
+ }).expect(result(200));
+
+ expect(response.body).to.have.property('nodes').length(5);
+ expect(response.body).to.have.property('edges').length(4);
+ expect(response.body).not.to.have.property('messages');
+
+ response.body.nodes.forEach((node: any) => {
+ expect(node).to.have.property('color');
+ expect(node.color).equal(
+ 'danger',
+ `node color mismatched [node: ${node.id}] [actual: ${node.color}]`
+ );
+ });
+
+ response.body.edges.forEach((edge: any) => {
+ expect(edge).to.have.property('color');
+ expect(edge.color).equal(
+ 'danger',
+ `edge color mismatched [edge: ${edge.id}] [actual: ${edge.color}]`
+ );
+ });
+ });
+
+ it('should return a graph with nodes and edges by alert and actor', async () => {
+ const response = await postGraph(supertest, {
+ query: {
+ eventIds: ['kabcd1234efgh5678'],
+ start: '2024-09-01T00:00:00Z',
+ end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ match_phrase: {
+ 'actor.entity.id': 'admin2@example.com',
+ },
+ },
+ ],
+ },
+ },
+ },
+ }).expect(result(200));
+
+ expect(response.body).to.have.property('nodes').length(5);
+ expect(response.body).to.have.property('edges').length(4);
+ expect(response.body).not.to.have.property('messages');
+
+ response.body.nodes.forEach((node: any, idx: number) => {
+ expect(node).to.have.property('color');
+ expect(node.color).equal(
+ idx <= 2 // First 3 nodes are expected to be colored as danger (ORDER MATTERS, alerts are expected to be first)
+ ? 'danger'
+ : node.shape === 'label' && node.id.includes('outcome(failed)')
+ ? 'warning'
+ : 'primary',
+ `node color mismatched [node: ${node.id}] [actual: ${node.color}]`
+ );
+ });
+
+ response.body.edges.forEach((edge: any, idx: number) => {
+ expect(edge).to.have.property('color');
+ expect(edge.color).equal(
+ idx <= 1 ? 'danger' : 'warning',
`edge color mismatched [edge: ${edge.id}] [actual: ${edge.color}]`
);
});
});
+
+ it('Should filter unknown targets', async () => {
+ const response = await postGraph(supertest, {
+ query: {
+ eventIds: [],
+ start: '2024-09-01T00:00:00Z',
+ end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ match_phrase: {
+ 'actor.entity.id': 'admin5@example.com',
+ },
+ },
+ ],
+ },
+ },
+ },
+ }).expect(result(200));
+
+ expect(response.body).to.have.property('nodes').length(0);
+ expect(response.body).to.have.property('edges').length(0);
+ expect(response.body).not.to.have.property('messages');
+ });
+
+ it('Should return unknown targets', async () => {
+ const response = await postGraph(supertest, {
+ showUnknownTarget: true,
+ query: {
+ eventIds: [],
+ start: '2024-09-01T00:00:00Z',
+ end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ match_phrase: {
+ 'actor.entity.id': 'admin5@example.com',
+ },
+ },
+ ],
+ },
+ },
+ },
+ }).expect(result(200));
+
+ expect(response.body).to.have.property('nodes').length(3);
+ expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).not.to.have.property('messages');
+ });
+
+ it('Should limit number of nodes', async () => {
+ const response = await postGraph(supertest, {
+ nodesLimit: 1,
+ query: {
+ eventIds: [],
+ start: '2024-09-01T00:00:00Z',
+ end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [
+ {
+ exists: {
+ field: 'actor.entity.id',
+ },
+ },
+ ],
+ },
+ },
+ },
+ }).expect(result(200));
+
+ expect(response.body).to.have.property('nodes').length(3); // Minimal number of nodes in a single relationship
+ expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).to.have.property('messages').length(1);
+ expect(response.body.messages[0]).equal(ApiMessageCode.ReachedNodesLimit);
+ });
});
});
}
diff --git a/x-pack/test/cloud_security_posture_api/utils.ts b/x-pack/test/cloud_security_posture_api/utils.ts
index e64c583af3868..210a081b91473 100644
--- a/x-pack/test/cloud_security_posture_api/utils.ts
+++ b/x-pack/test/cloud_security_posture_api/utils.ts
@@ -36,22 +36,23 @@ export const waitForPluginInitialized = ({
logger.debug('CSP plugin is initialized');
});
-export function result(status: number): CallbackHandler {
+export function result(status: number, logger?: ToolingLog): CallbackHandler {
return (err: any, res: Response) => {
if ((res?.status || err.status) !== status) {
- const e = new Error(
+ throw new Error(
`Expected ${status} ,got ${res?.status || err.status} resp: ${
res?.body ? JSON.stringify(res.body) : err.text
}`
);
- throw e;
+ } else if (err) {
+ logger?.warning(`Error result ${err.text}`);
}
};
}
export class EsIndexDataProvider {
private es: EsClient;
- private index: string;
+ private readonly index: string;
constructor(es: EsClient, index: string) {
this.es = es;
diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_with_streaming.ts b/x-pack/test/fleet_api_integration/apis/epm/install_with_streaming.ts
index 152e3dfd4c69d..4fa0e485be2b5 100644
--- a/x-pack/test/fleet_api_integration/apis/epm/install_with_streaming.ts
+++ b/x-pack/test/fleet_api_integration/apis/epm/install_with_streaming.ts
@@ -36,7 +36,8 @@ export default function (providerContext: FtrProviderContext) {
return res?._source?.['epm-packages'] as Installation;
};
- describe('Installs a package using stream-based approach', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/199701
+ describe.skip('Installs a package using stream-based approach', () => {
skipIfNoDockerRegistry(providerContext);
before(async () => {
diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts
index f257f76cbfc5b..8053293f98633 100644
--- a/x-pack/test/functional/page_objects/index_management_page.ts
+++ b/x-pack/test/functional/page_objects/index_management_page.ts
@@ -162,13 +162,13 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext)
},
async clickCreateIndexButton() {
await testSubjects.click('createIndexButton');
- await testSubjects.existOrFail('createIndexSaveButton');
},
async setCreateIndexName(value: string) {
await testSubjects.existOrFail('createIndexNameFieldText');
await testSubjects.setValue('createIndexNameFieldText', value);
},
async clickCreateIndexSaveButton() {
+ await testSubjects.existOrFail('createIndexSaveButton');
await testSubjects.click('createIndexSaveButton');
// Wait for modal to close
await testSubjects.missingOrFail('createIndexSaveButton', {
diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts
index 51b0842e60bc8..c5927d894911c 100644
--- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts
+++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts
@@ -114,6 +114,34 @@ export function initRoutes(
}
);
+ router.post(
+ {
+ path: `/api/sample_tasks/run_mark_removed_tasks_as_unrecognized`,
+ validate: {
+ body: schema.object({}),
+ },
+ },
+ async function (
+ context: RequestHandlerContext,
+ req: KibanaRequest,
+ res: KibanaResponseFactory
+ ): Promise> {
+ try {
+ const taskManager = await taskManagerStart;
+ await taskManager.ensureScheduled({
+ id: 'mark_removed_tasks_as_unrecognized',
+ taskType: 'task_manager:mark_removed_tasks_as_unrecognized',
+ schedule: { interval: '1h' },
+ state: {},
+ params: {},
+ });
+ return res.ok({ body: await taskManager.runSoon('mark_removed_tasks_as_unrecognized') });
+ } catch (err) {
+ return res.ok({ body: { id: 'mark_removed_tasks_as_unrecognized', error: `${err}` } });
+ }
+ }
+ );
+
router.post(
{
path: `/api/sample_tasks/bulk_enable`,
diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts
index 091e0fe01e415..c8056c2ee205e 100644
--- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts
@@ -168,6 +168,7 @@ export default function ({ getService }: FtrProviderContext) {
'security:telemetry-timelines',
'session_cleanup',
'task_manager:delete_inactive_background_task_nodes',
+ 'task_manager:mark_removed_tasks_as_unrecognized',
]);
});
});
diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts
index aae90a52572c7..a7447353e805a 100644
--- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts
@@ -92,6 +92,12 @@ export default function ({ getService }: FtrProviderContext) {
let scheduledTaskRuns = 0;
let scheduledTaskInstanceRunAt = scheduledTask.runAt;
+ await request
+ .post('/api/sample_tasks/run_mark_removed_tasks_as_unrecognized')
+ .set('kbn-xsrf', 'xxx')
+ .send({})
+ .expect(200);
+
await retry.try(async () => {
const tasks = (await currentTasks()).docs;
expect(tasks.length).to.eql(3);
diff --git a/x-pack/test/task_manager_claimer_update_by_query/plugins/sample_task_plugin_mget/server/init_routes.ts b/x-pack/test/task_manager_claimer_update_by_query/plugins/sample_task_plugin_mget/server/init_routes.ts
index f1e697399fe09..acdbae0b00337 100644
--- a/x-pack/test/task_manager_claimer_update_by_query/plugins/sample_task_plugin_mget/server/init_routes.ts
+++ b/x-pack/test/task_manager_claimer_update_by_query/plugins/sample_task_plugin_mget/server/init_routes.ts
@@ -117,6 +117,34 @@ export function initRoutes(
}
);
+ router.post(
+ {
+ path: `/api/sample_tasks/run_mark_removed_tasks_as_unrecognized`,
+ validate: {
+ body: schema.object({}),
+ },
+ },
+ async function (
+ context: RequestHandlerContext,
+ req: KibanaRequest,
+ res: KibanaResponseFactory
+ ): Promise> {
+ try {
+ const taskManager = await taskManagerStart;
+ await taskManager.ensureScheduled({
+ id: 'mark_removed_tasks_as_unrecognized',
+ taskType: 'task_manager:mark_removed_tasks_as_unrecognized',
+ schedule: { interval: '1h' },
+ state: {},
+ params: {},
+ });
+ return res.ok({ body: await taskManager.runSoon('mark_removed_tasks_as_unrecognized') });
+ } catch (err) {
+ return res.ok({ body: { id: 'mark_removed_tasks_as_unrecognized', error: `${err}` } });
+ }
+ }
+ );
+
router.post(
{
path: `/api/sample_tasks/bulk_enable`,
diff --git a/x-pack/test/task_manager_claimer_update_by_query/test_suites/task_manager/task_management_removed_types.ts b/x-pack/test/task_manager_claimer_update_by_query/test_suites/task_manager/task_management_removed_types.ts
index aae90a52572c7..a7447353e805a 100644
--- a/x-pack/test/task_manager_claimer_update_by_query/test_suites/task_manager/task_management_removed_types.ts
+++ b/x-pack/test/task_manager_claimer_update_by_query/test_suites/task_manager/task_management_removed_types.ts
@@ -92,6 +92,12 @@ export default function ({ getService }: FtrProviderContext) {
let scheduledTaskRuns = 0;
let scheduledTaskInstanceRunAt = scheduledTask.runAt;
+ await request
+ .post('/api/sample_tasks/run_mark_removed_tasks_as_unrecognized')
+ .set('kbn-xsrf', 'xxx')
+ .send({})
+ .expect(200);
+
await retry.try(async () => {
const tasks = (await currentTasks()).docs;
expect(tasks.length).to.eql(3);
diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts
index 741d25291e8fa..aaccdd0e9a41c 100644
--- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts
@@ -12,6 +12,7 @@ import {
} from '@kbn/core-http-common';
import { result } from '@kbn/test-suites-xpack/cloud_security_posture_api/utils';
import type { Agent } from 'supertest';
+import type { GraphRequest } from '@kbn/cloud-security-posture-common/types/graph/v1';
import type { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
@@ -19,7 +20,7 @@ export default function ({ getService }: FtrProviderContext) {
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestViewer: Pick;
- const postGraph = (agent: Pick, body: any) => {
+ const postGraph = (agent: Pick, body: GraphRequest) => {
const req = agent
.post('/internal/cloud_security_posture/graph')
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
@@ -48,7 +49,6 @@ export default function ({ getService }: FtrProviderContext) {
it('should return an empty graph', async () => {
const response = await postGraph(supertestViewer, {
query: {
- actorIds: [],
eventIds: [],
start: 'now-1d/d',
end: 'now/d',
@@ -57,20 +57,26 @@ export default function ({ getService }: FtrProviderContext) {
expect(response.body).to.have.property('nodes').length(0);
expect(response.body).to.have.property('edges').length(0);
+ expect(response.body).not.to.have.property('messages');
});
it('should return a graph with nodes and edges by actor', async () => {
const response = await postGraph(supertestViewer, {
query: {
- actorIds: ['admin@example.com'],
eventIds: [],
start: '2024-09-01T00:00:00Z',
end: '2024-09-02T00:00:00Z',
+ esQuery: {
+ bool: {
+ filter: [{ match_phrase: { 'actor.entity.id': 'admin@example.com' } }],
+ },
+ },
},
}).expect(result(200));
expect(response.body).to.have.property('nodes').length(3);
expect(response.body).to.have.property('edges').length(2);
+ expect(response.body).not.to.have.property('messages');
response.body.nodes.forEach((node: any) => {
expect(node).to.have.property('color');
diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts
index 0c6b776433b0c..2c894b28b065f 100644
--- a/x-pack/test_serverless/functional/page_objects/index.ts
+++ b/x-pack/test_serverless/functional/page_objects/index.ts
@@ -24,6 +24,7 @@ import { SvlSearchHomePageProvider } from './svl_search_homepage';
import { SvlSearchIndexDetailPageProvider } from './svl_search_index_detail_page';
import { SvlSearchElasticsearchStartPageProvider } from './svl_search_elasticsearch_start_page';
import { SvlApiKeysProvider } from './svl_api_keys';
+import { SvlSearchCreateIndexPageProvider } from './svl_search_create_index_page';
export const pageObjects = {
...xpackFunctionalPageObjects,
@@ -45,4 +46,5 @@ export const pageObjects = {
svlSearchIndexDetailPage: SvlSearchIndexDetailPageProvider,
svlSearchElasticsearchStartPage: SvlSearchElasticsearchStartPageProvider,
svlApiKeys: SvlApiKeysProvider,
+ svlSearchCreateIndexPage: SvlSearchCreateIndexPageProvider,
};
diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_create_index_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_create_index_page.ts
new file mode 100644
index 0000000000000..3d0d48f98a6a5
--- /dev/null
+++ b/x-pack/test_serverless/functional/page_objects/svl_search_create_index_page.ts
@@ -0,0 +1,106 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { FtrProviderContext } from '../ftr_provider_context';
+
+export function SvlSearchCreateIndexPageProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const browser = getService('browser');
+ const retry = getService('retry');
+
+ return {
+ async expectToBeOnCreateIndexPage() {
+ expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/indices/create');
+ await testSubjects.existOrFail('elasticsearchCreateIndexPage', { timeout: 2000 });
+ },
+ async expectToBeOnIndexDetailsPage() {
+ await retry.tryForTime(60 * 1000, async () => {
+ expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/indices/index_details');
+ });
+ },
+ async expectToBeOnIndexListPage() {
+ await retry.tryForTime(60 * 1000, async () => {
+ expect(await browser.getCurrentUrl()).contain(
+ '/app/management/data/index_management/indices'
+ );
+ });
+ },
+ async expectToBeOnMLFileUploadPage() {
+ await retry.tryForTime(60 * 1000, async () => {
+ expect(await browser.getCurrentUrl()).contain('/app/ml/filedatavisualizer');
+ });
+ },
+ async expectIndexNameToExist() {
+ await testSubjects.existOrFail('indexNameField');
+ },
+ async setIndexNameValue(value: string) {
+ await testSubjects.existOrFail('indexNameField');
+ await testSubjects.setValue('indexNameField', value);
+ },
+ async expectCloseCreateIndexButtonExists() {
+ await testSubjects.existOrFail('closeCreateIndex');
+ },
+ async clickCloseCreateIndexButton() {
+ await testSubjects.existOrFail('closeCreateIndex');
+ await testSubjects.click('closeCreateIndex');
+ },
+ async expectCreateIndexButtonToExist() {
+ await testSubjects.existOrFail('createIndexBtn');
+ },
+ async expectCreateIndexButtonToBeEnabled() {
+ await testSubjects.existOrFail('createIndexBtn');
+ expect(await testSubjects.isEnabled('createIndexBtn')).equal(true);
+ },
+ async expectCreateIndexButtonToBeDisabled() {
+ await testSubjects.existOrFail('createIndexBtn');
+ expect(await testSubjects.isEnabled('createIndexBtn')).equal(false);
+ },
+ async clickCreateIndexButton() {
+ await testSubjects.existOrFail('createIndexBtn');
+ expect(await testSubjects.isEnabled('createIndexBtn')).equal(true);
+ await testSubjects.click('createIndexBtn');
+ },
+ async expectCreateIndexCodeView() {
+ await testSubjects.existOrFail('createIndexCodeView');
+ },
+ async expectCreateIndexUIView() {
+ await testSubjects.existOrFail('createIndexUIView');
+ },
+ async clickUIViewButton() {
+ await testSubjects.existOrFail('createIndexUIViewBtn');
+ await testSubjects.click('createIndexUIViewBtn');
+ },
+ async clickCodeViewButton() {
+ await testSubjects.existOrFail('createIndexCodeViewBtn');
+ await testSubjects.click('createIndexCodeViewBtn');
+ },
+ async clickFileUploadLink() {
+ await testSubjects.existOrFail('uploadFileLink');
+ await testSubjects.click('uploadFileLink');
+ },
+ async expectAPIKeyVisibleInCodeBlock(apiKey: string) {
+ await testSubjects.existOrFail('createIndex-code-block');
+ await retry.try(async () => {
+ expect(await testSubjects.getVisibleText('createIndex-code-block')).to.contain(apiKey);
+ });
+ },
+
+ async expectAPIKeyPreGenerated() {
+ await testSubjects.existOrFail('apiKeyHasBeenGenerated');
+ },
+
+ async expectAPIKeyNotPreGenerated() {
+ await testSubjects.existOrFail('apiKeyHasNotBeenGenerated');
+ },
+
+ async expectAPIKeyFormNotAvailable() {
+ await testSubjects.missingOrFail('apiKeyHasNotBeenGenerated');
+ await testSubjects.missingOrFail('apiKeyHasBeenGenerated');
+ },
+ };
+}
diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_elasticsearch_start_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_elasticsearch_start_page.ts
index 3a3c9700cf2aa..aadb41d6f432a 100644
--- a/x-pack/test_serverless/functional/page_objects/svl_search_elasticsearch_start_page.ts
+++ b/x-pack/test_serverless/functional/page_objects/svl_search_elasticsearch_start_page.ts
@@ -42,6 +42,20 @@ export function SvlSearchElasticsearchStartPageProvider({ getService }: FtrProvi
await testSubjects.existOrFail('indexNameField');
await testSubjects.setValue('indexNameField', value);
},
+ async expectCloseCreateIndexButtonExists() {
+ await testSubjects.existOrFail('closeCreateIndex');
+ },
+ async clickCloseCreateIndexButton() {
+ await testSubjects.existOrFail('closeCreateIndex');
+ await testSubjects.click('closeCreateIndex');
+ },
+ async expectSkipButtonExists() {
+ await testSubjects.existOrFail('createIndexSkipBtn');
+ },
+ async clickSkipButton() {
+ await testSubjects.existOrFail('createIndexSkipBtn');
+ await testSubjects.click('createIndexSkipBtn');
+ },
async expectCreateIndexButtonToExist() {
await testSubjects.existOrFail('createIndexBtn');
},
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_detail.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_detail.ts
index f8734e610a61c..be3b683d9903a 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_detail.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_detail.ts
@@ -14,6 +14,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const security = getService('security');
const testIndexName = `index-ftr-test-${Math.random()}`;
describe('Index Details ', function () {
+ this.tags(['skipSvlSearch']);
before(async () => {
await security.testUser.setRoles(['index_management_user']);
await pageObjects.svlCommonPage.loginAsAdmin();
@@ -34,7 +35,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.indexManagement.expectIndexToExist(testIndexName);
});
describe('can view index details', function () {
- this.tags(['skipSvlSearch']);
it('index with no documents', async () => {
await pageObjects.indexManagement.indexDetailsPage.openIndexDetailsPage(0);
await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts
index e98fcc09e97d1..0d8f091123627 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts
@@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const es = getService('es');
describe('Indices', function () {
+ this.tags(['skipSvlSearch']);
before(async () => {
await security.testUser.setRoles(['index_management_user']);
await pageObjects.svlCommonPage.loginAsAdmin();
@@ -53,7 +54,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
after(async () => {
await esDeleteAllIndices(testIndexName);
});
- this.tags('skipSvlSearch');
it('navigates to overview', async () => {
await pageObjects.indexManagement.changeManageIndexTab('showOverviewIndexMenuButton');
await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();
diff --git a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
index 129f769283b34..39228137cf7d7 100644
--- a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
@@ -156,6 +156,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlSearchElasticsearchStartPage.expectAnalyzeLogsLink();
await pageObjects.svlSearchElasticsearchStartPage.expectO11yTrialLink();
});
+
+ it('should have close button', async () => {
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
+ await pageObjects.svlSearchElasticsearchStartPage.expectCloseCreateIndexButtonExists();
+ await pageObjects.svlSearchElasticsearchStartPage.clickCloseCreateIndexButton();
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexListPage();
+ });
+ it('should have skip button', async () => {
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
+ await pageObjects.svlSearchElasticsearchStartPage.expectSkipButtonExists();
+ await pageObjects.svlSearchElasticsearchStartPage.clickSkipButton();
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexListPage();
+ });
});
describe('viewer', function () {
before(async () => {
diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts
index 903f98c63b776..99190ae0cc072 100644
--- a/x-pack/test_serverless/functional/test_suites/search/index.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/index.ts
@@ -15,6 +15,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./elasticsearch_start.ts'));
loadTestFile(require.resolve('./search_index_detail.ts'));
loadTestFile(require.resolve('./getting_started'));
+ loadTestFile(require.resolve('./index_management'));
loadTestFile(require.resolve('./connectors/connectors_overview'));
loadTestFile(require.resolve('./default_dataview'));
loadTestFile(require.resolve('./pipelines'));
diff --git a/x-pack/test_serverless/functional/test_suites/search/index_management.ts b/x-pack/test_serverless/functional/test_suites/search/index_management.ts
index ed7f09eecb0e8..08b093f660640 100644
--- a/x-pack/test_serverless/functional/test_suites/search/index_management.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/index_management.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { testHasEmbeddedConsole } from './embedded_console';
@@ -16,11 +17,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'common',
'header',
'indexManagement',
+ 'svlSearchCreateIndexPage',
]);
+ const browser = getService('browser');
const security = getService('security');
+ const es = getService('es');
const esDeleteAllIndices = getService('esDeleteAllIndices');
const testIndexName = `test-index-ftr-${Math.random()}`;
+ const testAPIIndexName = `test-api-index-ftr-${Math.random()}`;
describe('index management', function () {
before(async () => {
await security.testUser.setRoles(['index_management_user']);
@@ -32,23 +37,81 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.header.waitUntilLoadingHasFinished();
});
after(async () => {
- await esDeleteAllIndices(testIndexName);
+ await esDeleteAllIndices([testIndexName, testAPIIndexName]);
+ });
+
+ it('renders the indices tab', async () => {
+ const url = await browser.getCurrentUrl();
+ expect(url).to.contain(`/indices`);
});
it('has embedded dev console', async () => {
await testHasEmbeddedConsole(pageObjects);
});
- it('can create an index', async () => {
- await pageObjects.indexManagement.clickCreateIndexButton();
- await pageObjects.indexManagement.setCreateIndexName(testIndexName);
- await pageObjects.indexManagement.clickCreateIndexSaveButton();
- await pageObjects.indexManagement.expectIndexToExist(testIndexName);
+ describe('create index', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('indexManagement');
+ // Navigate to the indices tab
+ await pageObjects.indexManagement.changeTabs('indicesTab');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+ it('can create an index', async () => {
+ await pageObjects.indexManagement.clickCreateIndexButton();
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnCreateIndexPage();
+ await pageObjects.svlSearchCreateIndexPage.expectCreateIndexUIView();
+ await pageObjects.svlSearchCreateIndexPage.expectCreateIndexButtonToBeEnabled();
+ await pageObjects.svlSearchCreateIndexPage.setIndexNameValue(testIndexName);
+ await pageObjects.svlSearchCreateIndexPage.clickCreateIndexButton();
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnIndexDetailsPage();
+ await pageObjects.common.navigateToApp('indexManagement');
+ await pageObjects.indexManagement.changeTabs('indicesTab');
+ await pageObjects.indexManagement.expectIndexToExist(testIndexName);
+ });
+ it('should redirect to index details when index is created via API and on the code view', async () => {
+ await pageObjects.indexManagement.clickCreateIndexButton();
+
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnCreateIndexPage();
+ await pageObjects.svlSearchCreateIndexPage.expectCreateIndexUIView();
+ await pageObjects.svlSearchCreateIndexPage.clickCodeViewButton();
+ await pageObjects.svlSearchCreateIndexPage.expectCreateIndexCodeView();
+ await es.indices.create({ index: testAPIIndexName });
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnIndexDetailsPage();
+ });
+ it('should have file upload link', async () => {
+ await pageObjects.indexManagement.clickCreateIndexButton();
+
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnCreateIndexPage();
+ await pageObjects.svlSearchCreateIndexPage.clickFileUploadLink();
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnMLFileUploadPage();
+ });
+ it('should support closing create index page', async () => {
+ await pageObjects.indexManagement.clickCreateIndexButton();
+
+ await pageObjects.svlSearchCreateIndexPage.expectCloseCreateIndexButtonExists();
+ await pageObjects.svlSearchCreateIndexPage.clickCloseCreateIndexButton();
+ await pageObjects.svlSearchCreateIndexPage.expectToBeOnIndexListPage();
+ });
+ it('should have the embedded console', async () => {
+ await pageObjects.indexManagement.clickCreateIndexButton();
+
+ await testHasEmbeddedConsole(pageObjects);
+ });
});
- it('can view index details - index with no documents', async () => {
- await pageObjects.indexManagement.indexDetailsPage.openIndexDetailsPage(0);
- await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();
+ describe('manage index', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('indexManagement');
+ // Navigate to the indices tab
+ await pageObjects.indexManagement.changeTabs('indicesTab');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await pageObjects.indexManagement.manageIndex(testIndexName);
+ await pageObjects.indexManagement.manageIndexContextMenuExists();
+ });
+ it('can delete index', async () => {
+ await pageObjects.indexManagement.confirmDeleteModalIsVisible();
+ await pageObjects.indexManagement.expectIndexIsDeleted(testIndexName);
+ });
});
});
}
diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts
index a6da7b1467e9a..97952d68f8fd1 100644
--- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts
@@ -36,10 +36,11 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
// check side nav links
await solutionNavigation.sidenav.expectSectionExists('search_project_nav');
await solutionNavigation.sidenav.expectLinkActive({
- deepLinkId: 'elasticsearchStart',
+ deepLinkId: 'management:index_management',
});
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
- deepLinkId: 'elasticsearchStart',
+ text: 'Create your first index',
});
await testSubjects.existOrFail(`elasticsearchStartPage`);
@@ -53,6 +54,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Data' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
// > Connectors
await solutionNavigation.sidenav.clickLink({
@@ -176,9 +178,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
// navigate back to serverless search overview
await svlCommonNavigation.clickLogo();
await svlCommonNavigation.sidenav.expectLinkActive({
- deepLinkId: 'elasticsearchStart',
+ deepLinkId: 'management:index_management',
});
- await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Home` });
+ await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Indices` });
await testSubjects.existOrFail(`elasticsearchStartPage`);
await expectNoPageReload();
@@ -236,7 +238,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
it('renders expected side navigation items', async () => {
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
// Verify all expected top-level links exist
- await solutionNavigation.sidenav.expectLinkExists({ text: 'Home' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Data' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Index Management' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Connectors' });
@@ -261,7 +262,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
await solutionNavigation.sidenav.expectOnlyDefinedLinks([
'search_project_nav',
- 'home',
'data',
'management:index_management',
'serverlessConnectors',
diff --git a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts
index e1570d2191378..946afe08a0b73 100644
--- a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts
@@ -69,7 +69,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundHeaderComponentsToExist();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundHeaderComponentsToDisabled();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageComponentsToExist();
- await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageIndexCalloutExists();
+ await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageIndexButtonExists();
});
describe('with gen ai connectors', () => {
@@ -106,7 +106,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
describe('without any indices', () => {
- it('hide no index callout when index added', async () => {
+ it('hide no create index button when index added', async () => {
await createIndex();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectOpenFlyoutAndSelectIndex();
});
diff --git a/yarn.lock b/yarn.lock
index 5faf426cf4d25..47ee0df930999 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1721,12 +1721,12 @@
"@elastic/transport" "^8.3.1"
tslib "^2.4.0"
-"@elastic/elasticsearch@^8.15.0":
- version "8.15.0"
- resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-8.15.0.tgz#cb29b3ae33203c545d435cf3dc4b557c8b4961d5"
- integrity sha512-mG90EMdTDoT6GFSdqpUAhWK9LGuiJo6tOWqs0Usd/t15mPQDj7ZqHXfCBqNkASZpwPZpbAYVjd57S6nbUBINCg==
+"@elastic/elasticsearch@^8.15.1":
+ version "8.15.1"
+ resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-8.15.1.tgz#ca294ba11ed1514bf87d4a2e253b11f6cefd8552"
+ integrity sha512-L3YzSaxrasMMGtcxnktiUDjS5f177L0zpHsBH+jL0LgPhdMk9xN/VKrAaYzvri86IlV5IbveA0ANV6o/BDUmhQ==
dependencies:
- "@elastic/transport" "^8.7.0"
+ "@elastic/transport" "^8.8.1"
tslib "^2.4.0"
"@elastic/ems-client@8.5.3":
@@ -1906,10 +1906,10 @@
undici "^5.28.3"
yaml "^2.2.2"
-"@elastic/transport@^8.3.1", "@elastic/transport@^8.7.0":
- version "8.7.0"
- resolved "https://registry.yarnpkg.com/@elastic/transport/-/transport-8.7.0.tgz#006987fc5583f61c266e0b1003371e82efc7a6b5"
- integrity sha512-IqXT7a8DZPJtqP2qmX1I2QKmxYyN27kvSW4g6pInESE1SuGwZDp2FxHJ6W2kwmYOJwQdAt+2aWwzXO5jHo9l4A==
+"@elastic/transport@^8.3.1", "@elastic/transport@^8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@elastic/transport/-/transport-8.8.1.tgz#d64244907bccdad5626c860b492faeef12194b1f"
+ integrity sha512-4RQIiChwNIx3B0O+2JdmTq/Qobj6+1g2RQnSv1gt4V2SVfAYjGwOKu0ZMKEHQOXYNG6+j/Chero2G9k3/wXLEw==
dependencies:
"@opentelemetry/api" "1.x"
debug "^4.3.4"
@@ -8483,12 +8483,12 @@
require-from-string "^2.0.2"
uri-js-replace "^1.0.1"
-"@redocly/cli@^1.25.10":
- version "1.25.10"
- resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.25.10.tgz#647e33e4171d74a4f879304ba87366ac650ed83d"
- integrity sha512-zoRMvSYOLzurcb3be5HLLlc5dLGICyHY8mueCbdE2DmLbFERhJJ5iiABKvNRJSr03AR6X569f4mraBJpAsGJnQ==
+"@redocly/cli@^1.25.11":
+ version "1.25.11"
+ resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.25.11.tgz#8ec17a6535aebfd166e8cab8ffcc9d768af1b014"
+ integrity sha512-dttBsmLnnbTlJCTa+s7Sy+qtXDq692n7Ru3nUUIHp9XdCbhXIHWhpc8uAl+GmR4MGbVe8ohATl3J+zX3aFy82A==
dependencies:
- "@redocly/openapi-core" "1.25.10"
+ "@redocly/openapi-core" "1.25.11"
abort-controller "^3.0.0"
chokidar "^3.5.1"
colorette "^1.2.0"
@@ -8513,10 +8513,10 @@
resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.16.0.tgz#4b7700a5cb6e04bc6d6fdb94b871c9e260a1fba6"
integrity sha512-t9jnODbUcuANRSl/K4L9nb12V+U5acIHnVSl26NWrtSdDZVtoqUXk2yGFPZzohYf62cCfEQUT8ouJ3bhPfpnJg==
-"@redocly/openapi-core@1.25.10", "@redocly/openapi-core@^1.4.0":
- version "1.25.10"
- resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.25.10.tgz#6ca3f1ad1b826e3680f91752abf11aa40856f6b8"
- integrity sha512-wcGnSonJZvjpPaJJs+qh0ADYy0aCbaNhCXhJVES9RlknMc7V9nbqLQ67lkwaXhpp/fskm9GJWL/U9Xyiuclbqw==
+"@redocly/openapi-core@1.25.11", "@redocly/openapi-core@^1.4.0":
+ version "1.25.11"
+ resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.25.11.tgz#93f168284986da6809363b001e9aa7c2104c2fc0"
+ integrity sha512-bH+a8izQz4fnKROKoX3bEU8sQ9rjvEIZOqU6qTmxlhOJ0NsKa5e+LmU18SV0oFeg5YhWQhhEDihXkvKJ1wMMNQ==
dependencies:
"@redocly/ajv" "^8.11.2"
"@redocly/config" "^0.16.0"