diff --git a/examples/embeddable_examples/kibana.jsonc b/examples/embeddable_examples/kibana.jsonc
index b3b63480e1103e..f0d4e829747f76 100644
--- a/examples/embeddable_examples/kibana.jsonc
+++ b/examples/embeddable_examples/kibana.jsonc
@@ -18,6 +18,6 @@
"developerExamples",
"dataViewFieldEditor"
],
- "requiredBundles": ["presentationUtil", "kibanaUtils", "kibanaReact"]
+ "requiredBundles": ["dashboard", "presentationUtil", "kibanaUtils", "kibanaReact"]
}
}
diff --git a/examples/embeddable_examples/public/app/register_embeddable.tsx b/examples/embeddable_examples/public/app/register_embeddable.tsx
index e9116a611d0ed1..95bac13ee0023b 100644
--- a/examples/embeddable_examples/public/app/register_embeddable.tsx
+++ b/examples/embeddable_examples/public/app/register_embeddable.tsx
@@ -12,11 +12,16 @@ import { EuiCodeBlock, EuiSpacer, EuiText } from '@elastic/eui';
import registerSearchEmbeddableSource from '!!raw-loader!../react_embeddables/search/register_search_embeddable';
// @ts-ignore
import registerAttachActionSource from '!!raw-loader!../react_embeddables/search/register_add_search_panel_action';
+// @ts-ignore
+import registerFieldListEmbeddableSource from '!!raw-loader!../react_embeddables/field_list/register_field_list_embeddable';
+// @ts-ignore
+import registerReactEmbeddableSavedObjectSource from '!!raw-loader!../react_embeddables/register_saved_object_example';
export const RegisterEmbeddable = () => {
return (
<>
+ Register a new embeddable type
This plugin registers several embeddable types with{' '}
registerReactEmbeddableFactory during plugin start. The code example
@@ -24,7 +29,7 @@ export const RegisterEmbeddable = () => {
asynchronously to limit initial page load size.
-
+
{registerSearchEmbeddableSource}
@@ -36,6 +41,12 @@ export const RegisterEmbeddable = () => {
Run the example embeddables by creating a dashboard, clicking Add panel button,
and then selecting Embeddable examples group.
+
+
+
+
+
+ Show embeddables in the Add panel menu
Add your own embeddables to Add panel menu by attaching an action to the{' '}
ADD_PANEL_TRIGGER trigger. Notice usage of grouping to
@@ -43,10 +54,45 @@ export const RegisterEmbeddable = () => {
@elastic/kibana-presentation team to coordinate menu updates.
-
+
{registerAttachActionSource}
+
+
+
+
+ Configure initial dashboard placement (optional)
+
+ Add an entry to registerDashboardPanelPlacementSetting to configure
+ initial dashboard placement. Panel placement lets you configure the width, height, and
+ placement strategy when panels get added to a dashboard. In the example below, the Field
+ List embeddable will be added to dashboards as a narrow and tall panel.
+
+
+
+
+
+ {registerFieldListEmbeddableSource}
+
+
+
+
+
+ Saved object embeddables
+
+ Embeddable factories, such as Lens, Maps, Links, that can reference saved objects should
+ register their saved object types using{' '}
+ registerReactEmbeddableSavedObject . The Add from library flyout
+ on Dashboards uses this registry to list saved objects. The example function below could
+ be called from the public start contract for a plugin.
+
+
+
+
+
+ {registerReactEmbeddableSavedObjectSource}
+
>
);
};
diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts
index e588919591724f..6939271e373413 100644
--- a/examples/embeddable_examples/public/plugin.ts
+++ b/examples/embeddable_examples/public/plugin.ts
@@ -24,8 +24,9 @@ import { DATA_TABLE_ID } from './react_embeddables/data_table/constants';
import { registerCreateDataTableAction } from './react_embeddables/data_table/create_data_table_action';
import { EUI_MARKDOWN_ID } from './react_embeddables/eui_markdown/constants';
import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action';
-import { FIELD_LIST_ID } from './react_embeddables/field_list/constants';
import { registerCreateFieldListAction } from './react_embeddables/field_list/create_field_list_action';
+import { FIELD_LIST_ID } from './react_embeddables/field_list/constants';
+import { registerFieldListPanelPlacementSetting } from './react_embeddables/field_list/register_field_list_embeddable';
import { registerAddSearchPanelAction } from './react_embeddables/search/register_add_search_panel_action';
import { registerSearchEmbeddable } from './react_embeddables/search/register_search_embeddable';
@@ -58,6 +59,7 @@ export class EmbeddableExamplesPlugin implements Plugin {
diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx
index 0c7d3d127efb93..2931760310c2b4 100644
--- a/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx
+++ b/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx
@@ -8,17 +8,11 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/react';
-import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { Reference } from '@kbn/content-management-utils';
import { CoreStart } from '@kbn/core-lifecycle-browser';
-import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/common';
-import {
- DataViewsPublicPluginStart,
- DATA_VIEW_SAVED_OBJECT_TYPE,
-} from '@kbn/data-views-plugin/public';
+import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/public';
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
-import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { i18n } from '@kbn/i18n';
import { initializeTitles, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { LazyDataViewPicker, withSuspense } from '@kbn/presentation-util-plugin/public';
@@ -31,7 +25,7 @@ import { cloneDeep } from 'lodash';
import React, { useEffect } from 'react';
import { BehaviorSubject, skip, Subscription, switchMap } from 'rxjs';
import { FIELD_LIST_DATA_VIEW_REF_NAME, FIELD_LIST_ID } from './constants';
-import { FieldListApi, FieldListSerializedStateState } from './types';
+import { FieldListApi, Services, FieldListSerializedStateState } from './types';
const DataViewPicker = withSuspense(LazyDataViewPicker, null);
@@ -48,17 +42,7 @@ const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOpti
export const getFieldListFactory = (
core: CoreStart,
- {
- dataViews,
- data,
- charts,
- fieldFormats,
- }: {
- dataViews: DataViewsPublicPluginStart;
- data: DataPublicPluginStart;
- charts: ChartsPluginStart;
- fieldFormats: FieldFormatsStart;
- }
+ { dataViews, data, charts, fieldFormats }: Services
) => {
const fieldListEmbeddableFactory: ReactEmbeddableFactory<
FieldListSerializedStateState,
diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/register_field_list_embeddable.ts b/examples/embeddable_examples/public/react_embeddables/field_list/register_field_list_embeddable.ts
new file mode 100644
index 00000000000000..e31df63b62f378
--- /dev/null
+++ b/examples/embeddable_examples/public/react_embeddables/field_list/register_field_list_embeddable.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import {
+ registerDashboardPanelPlacementSetting,
+ PanelPlacementStrategy,
+} from '@kbn/dashboard-plugin/public';
+import { FIELD_LIST_ID } from './constants';
+import { FieldListSerializedStateState } from './types';
+
+const getPanelPlacementSetting = (serializedState?: FieldListSerializedStateState) => {
+ // Consider using the serialized state to determine the width, height, and strategy
+ return {
+ width: 12,
+ height: 36,
+ strategy: PanelPlacementStrategy.placeAtTop,
+ };
+};
+
+export function registerFieldListPanelPlacementSetting() {
+ registerDashboardPanelPlacementSetting(FIELD_LIST_ID, getPanelPlacementSetting);
+}
diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/types.ts b/examples/embeddable_examples/public/react_embeddables/field_list/types.ts
index 0dd6651c577069..0da67bcd0f70b2 100644
--- a/examples/embeddable_examples/public/react_embeddables/field_list/types.ts
+++ b/examples/embeddable_examples/public/react_embeddables/field_list/types.ts
@@ -6,7 +6,11 @@
* Side Public License, v 1.
*/
+import { ChartsPluginStart } from '@kbn/charts-plugin/public';
+import { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
+import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { PublishesDataViews, SerializedTitles } from '@kbn/presentation-publishing';
import { PublishesSelectedFields } from './publishes_selected_fields';
@@ -16,3 +20,10 @@ export type FieldListSerializedStateState = SerializedTitles & {
};
export type FieldListApi = DefaultEmbeddableApi & PublishesSelectedFields & PublishesDataViews;
+
+export interface Services {
+ dataViews: DataViewsPublicPluginStart;
+ data: DataPublicPluginStart;
+ charts: ChartsPluginStart;
+ fieldFormats: FieldFormatsStart;
+}
diff --git a/examples/embeddable_examples/public/react_embeddables/register_saved_object_example.ts b/examples/embeddable_examples/public/react_embeddables/register_saved_object_example.ts
new file mode 100644
index 00000000000000..d2263240519933
--- /dev/null
+++ b/examples/embeddable_examples/public/react_embeddables/register_saved_object_example.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { registerReactEmbeddableSavedObject } from '@kbn/embeddable-plugin/public';
+
+const MY_EMBEDDABLE_TYPE = 'myEmbeddableType';
+const MY_SAVED_OBJECT_TYPE = 'mySavedObjectType';
+const APP_ICON = 'logoKibana';
+
+export const registerMyEmbeddableSavedObject = () =>
+ registerReactEmbeddableSavedObject({
+ onAdd: (container, savedObject) => {
+ container.addNewPanel({
+ panelType: MY_EMBEDDABLE_TYPE,
+ initialState: savedObject.attributes,
+ });
+ },
+ embeddableType: MY_EMBEDDABLE_TYPE,
+ savedObjectType: MY_SAVED_OBJECT_TYPE,
+ savedObjectName: 'Some saved object',
+ getIconForSavedObject: () => APP_ICON,
+ });
diff --git a/examples/embeddable_examples/tsconfig.json b/examples/embeddable_examples/tsconfig.json
index 30ac39afe90b75..b356083a20546d 100644
--- a/examples/embeddable_examples/tsconfig.json
+++ b/examples/embeddable_examples/tsconfig.json
@@ -15,6 +15,7 @@
"kbn_references": [
"@kbn/core",
"@kbn/ui-actions-plugin",
+ "@kbn/dashboard-plugin",
"@kbn/embeddable-plugin",
"@kbn/presentation-publishing",
"@kbn/ui-theme",
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index 017d5c6b233054..689f8a5e9859e1 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -480,6 +480,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
privileges: `${SECURITY_SOLUTION_DOCS}endpoint-management-req.html`,
manageDetectionRules: `${SECURITY_SOLUTION_DOCS}rules-ui-management.html`,
createEsqlRuleType: `${SECURITY_SOLUTION_DOCS}rules-ui-create.html#create-esql-rule`,
+ ruleUiAdvancedParams: `${SECURITY_SOLUTION_DOCS}rules-ui-create.html#rule-ui-advanced-params`,
entityAnalytics: {
riskScorePrerequisites: `${SECURITY_SOLUTION_DOCS}ers-requirements.html`,
hostRiskScore: `${SECURITY_SOLUTION_DOCS}host-risk-score.html`,
diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts
index bd8f353c1c5915..261165dcd7ec97 100644
--- a/packages/kbn-doc-links/src/types.ts
+++ b/packages/kbn-doc-links/src/types.ts
@@ -355,6 +355,7 @@ export interface DocLinks {
readonly privileges: string;
readonly manageDetectionRules: string;
readonly createEsqlRuleType: string;
+ readonly ruleUiAdvancedParams: string;
readonly entityAnalytics: {
readonly riskScorePrerequisites: string;
readonly hostRiskScore: string;
diff --git a/packages/kbn-eslint-config/typescript.js b/packages/kbn-eslint-config/typescript.js
index d62314c97d547c..d1503a196439b3 100644
--- a/packages/kbn-eslint-config/typescript.js
+++ b/packages/kbn-eslint-config/typescript.js
@@ -16,12 +16,7 @@ module.exports = {
files: ['**/*.{ts,tsx}'],
parser: '@typescript-eslint/parser',
- plugins: [
- '@typescript-eslint',
- 'ban',
- 'import',
- 'eslint-comments'
- ],
+ plugins: ['@typescript-eslint', 'ban', 'import', 'eslint-comments'],
env: {
es6: true,
@@ -34,7 +29,7 @@ module.exports = {
sourceType: 'module',
ecmaVersion: 2018,
ecmaFeatures: {
- jsx: true
+ jsx: true,
},
// NOTE: That is to avoid a known performance issue related with the `ts.Program` used by
// typescript eslint. As we are not using rules that need types information, we can safely
@@ -43,7 +38,7 @@ module.exports = {
// https://github.com/typescript-eslint/typescript-eslint/issues/389
// https://github.com/typescript-eslint/typescript-eslint/issues/243
// https://github.com/typescript-eslint/typescript-eslint/pull/361
- project: undefined
+ project: undefined,
},
// NOTE: we can't override the extends option here to apply
@@ -60,32 +55,38 @@ module.exports = {
//
// Old recommended tslint rules
'@typescript-eslint/adjacent-overload-signatures': 'error',
- '@typescript-eslint/array-type': ['error', { default: 'array-simple', readonly: 'array-simple' }],
- '@typescript-eslint/ban-types': ['error', {
- types: {
- SFC: {
- message: 'Use FC or FunctionComponent instead.',
- fixWith: 'FC'
- },
- 'React.SFC': {
- message: 'Use FC or FunctionComponent instead.',
- fixWith: 'React.FC'
- },
- StatelessComponent: {
- message: 'Use FunctionComponent instead.',
- fixWith: 'FunctionComponent'
- },
- 'React.StatelessComponent': {
- message: 'Use FunctionComponent instead.',
- fixWith: 'React.FunctionComponent'
+ '@typescript-eslint/array-type': [
+ 'error',
+ { default: 'array-simple', readonly: 'array-simple' },
+ ],
+ '@typescript-eslint/ban-types': [
+ 'error',
+ {
+ types: {
+ SFC: {
+ message: 'Use FC or FunctionComponent instead.',
+ fixWith: 'FC',
+ },
+ 'React.SFC': {
+ message: 'Use FC or FunctionComponent instead.',
+ fixWith: 'React.FC',
+ },
+ StatelessComponent: {
+ message: 'Use FunctionComponent instead.',
+ fixWith: 'FunctionComponent',
+ },
+ 'React.StatelessComponent': {
+ message: 'Use FunctionComponent instead.',
+ fixWith: 'React.FunctionComponent',
+ },
+ // used in the codebase in the wild
+ '{}': false,
+ object: false,
+ Function: false,
},
- // used in the codebase in the wild
- '{}': false,
- 'object': false,
- 'Function': false,
- }
- }],
- 'camelcase': 'off',
+ },
+ ],
+ camelcase: 'off',
'@typescript-eslint/naming-convention': [
'error',
{
@@ -93,8 +94,8 @@ module.exports = {
format: ['camelCase'],
filter: {
regex: allowedNameRegexp,
- match: false
- }
+ match: false,
+ },
},
{
selector: 'variable',
@@ -105,19 +106,16 @@ module.exports = {
],
filter: {
regex: allowedNameRegexp,
- match: false
- }
+ match: false,
+ },
},
{
selector: 'parameter',
- format: [
- 'camelCase',
- 'PascalCase',
- ],
+ format: ['camelCase', 'PascalCase'],
filter: {
regex: allowedNameRegexp,
- match: false
- }
+ match: false,
+ },
},
{
selector: 'memberLike',
@@ -125,23 +123,23 @@ module.exports = {
'camelCase',
'PascalCase',
'snake_case', // keys in elasticsearch requests / responses
- 'UPPER_CASE'
+ 'UPPER_CASE',
],
filter: {
regex: allowedNameRegexp,
- match: false
- }
+ match: false,
+ },
},
{
selector: 'function',
format: [
'camelCase',
- 'PascalCase' // React.FunctionComponent =
+ 'PascalCase', // React.FunctionComponent =
],
filter: {
regex: allowedNameRegexp,
- match: false
- }
+ match: false,
+ },
},
{
selector: 'typeLike',
@@ -164,27 +162,31 @@ module.exports = {
'objectLiteralMethod',
'typeMethod',
'accessor',
- 'enumMember'
+ 'enumMember',
],
format: null,
- modifiers: ['requiresQuotes']
- }
+ modifiers: ['requiresQuotes'],
+ },
],
- '@typescript-eslint/explicit-member-accessibility': ['error',
+ '@typescript-eslint/explicit-member-accessibility': [
+ 'error',
{
accessibility: 'off',
overrides: {
accessors: 'explicit',
constructors: 'no-public',
- parameterProperties: 'explicit'
- }
- }
+ parameterProperties: 'explicit',
+ },
+ },
],
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
- '@typescript-eslint/member-ordering': ['error', {
- 'default': ['public-static-field', 'static-field', 'instance-field']
- }],
+ '@typescript-eslint/member-ordering': [
+ 'error',
+ {
+ default: ['public-static-field', 'static-field', 'instance-field'],
+ },
+ ],
'@typescript-eslint/consistent-type-assertions': 'error',
'@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-extra-non-null-assertion': 'error',
@@ -195,24 +197,26 @@ module.exports = {
'@typescript-eslint/no-undef': 'off',
'no-undef': 'off',
- '@typescript-eslint/triple-slash-reference': ['error', {
- path: 'never',
- types: 'never',
- lib: 'never'
- }],
+ '@typescript-eslint/triple-slash-reference': [
+ 'error',
+ {
+ path: 'never',
+ types: 'never',
+ lib: 'never',
+ },
+ ],
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/unified-signatures': 'error',
'constructor-super': 'error',
'dot-notation': 'error',
- 'eqeqeq': ['error', 'always', {'null': 'ignore'}],
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
'guard-for-in': 'error',
- 'import/order': ['error', {
- 'groups': [
- ['external', 'builtin'],
- 'internal',
- ['parent', 'sibling', 'index'],
- ],
- }],
+ 'import/order': [
+ 'error',
+ {
+ groups: [['external', 'builtin'], 'internal', ['parent', 'sibling', 'index']],
+ },
+ ],
'max-classes-per-file': ['error', 1],
'no-bitwise': 'error',
'no-caller': 'error',
@@ -233,22 +237,27 @@ module.exports = {
'no-unused-labels': 'error',
'no-var': 'error',
'object-shorthand': 'error',
- 'one-var': [ 'error', 'never' ],
+ 'one-var': ['error', 'never'],
'prefer-const': 'error',
'prefer-rest-params': 'error',
- 'radix': 'error',
- 'spaced-comment': ["error", "always", {
- "exceptions": ["/"]
- }],
+ radix: 'error',
+ 'spaced-comment': [
+ 'error',
+ 'always',
+ {
+ exceptions: ['/'],
+ },
+ ],
'use-isnan': 'error',
// Old tslint yml override or defined rules
'ban/ban': [
2,
- {'name': ['describe', 'only'], 'message': 'No exclusive suites.'},
- {'name': ['it', 'only'], 'message': 'No exclusive tests.'},
- {'name': ['test', 'only'], 'message': 'No exclusive tests.'},
-
+ { name: ['describe', 'only'], message: 'No exclusive suites.' },
+ { name: ['it', 'only'], message: 'No exclusive tests.' },
+ { name: ['test', 'only'], message: 'No exclusive tests.' },
+ { name: ['testSuggestions', 'only'], message: 'No exclusive tests.' },
+ { name: ['testErrorsAndWarnings', 'only'], message: 'No exclusive tests.' },
],
'import/no-default-export': 'error',
@@ -257,13 +266,13 @@ module.exports = {
'no-restricted-syntax': [
'error',
{
- "selector": "TSEnumDeclaration[const=true]",
- "message": "Do not use `const` with enum declarations"
- }
- ]
+ selector: 'TSEnumDeclaration[const=true]',
+ message: 'Do not use `const` with enum declarations',
+ },
+ ],
},
eslintConfigPrettierRules
- )
+ ),
},
- ]
+ ],
};
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
index 4be855868d39b9..1f4353b9df836c 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
@@ -12,10 +12,9 @@ import { builtinFunctions } from '../definitions/builtin';
import { statsAggregationFunctionDefinitions } from '../definitions/aggs';
import { chronoLiterals, timeLiterals } from '../definitions/literals';
import { commandDefinitions } from '../definitions/commands';
-import { TRIGGER_SUGGESTION_COMMAND } from './factories';
+import { getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories';
import { camelCase } from 'lodash';
import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
-import { SuggestionRawDefinition } from './types';
import { groupingFunctionDefinitions } from '../definitions/grouping';
const triggerCharacters = [',', '(', '=', ' '];
@@ -231,14 +230,14 @@ function getPolicyFields(policyName: string) {
describe('autocomplete', () => {
type TestArgs = [
string,
- Array>,
+ string[],
(string | number)?,
Parameters?
];
const testSuggestionsFn = (
statement: string,
- expected: Array>,
+ expected: string[],
triggerCharacter: string | number = '',
customCallbacksArgs: Parameters = [
undefined,
@@ -271,28 +270,20 @@ describe('autocomplete', () => {
async (text) => (text ? getAstAndSyntaxErrors(text) : { ast: [], errors: [] }),
callbackMocks
);
- const suggestionInertTextSorted = suggestions
- // simulate the editor behaviour for sorting suggestions
- // copied from https://github.com/microsoft/vscode/blob/0a141d23179c76c5771df25a43546d9d9b6ed71c/src/vs/workbench/contrib/testing/browser/testingDecorations.ts#L971-L972
- // still not sure how accurate this is...
- .sort((a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label));
- expect(suggestionInertTextSorted).toHaveLength(expected.length);
- for (const [index, receivedSuggestion] of suggestionInertTextSorted.entries()) {
- if (typeof expected[index] !== 'object') {
- expect(receivedSuggestion.text).toEqual(expected[index]);
- } else {
- // check all properties that are defined in the expected suggestion
- for (const [key, value] of Object.entries(expected[index])) {
- expect(receivedSuggestion[key as keyof SuggestionRawDefinition]).toEqual(value);
- }
- }
- }
+ const sortedSuggestions = suggestions.map((suggestion) => suggestion.text).sort();
+ const sortedExpected = expected.sort();
+
+ expect(sortedSuggestions).toEqual(sortedExpected);
}
);
};
// Enrich the function to work with .only and .skip as regular test function
+ //
+ // DO NOT CHANGE THE NAME OF THIS FUNCTION WITHOUT ALSO CHANGING
+ // THE LINTER RULE IN packages/kbn-eslint-config/typescript.js
+ //
const testSuggestions = Object.assign(testSuggestionsFn, {
skip: (...args: TestArgs) => {
const paddingArgs = ['', [undefined, undefined, undefined]].slice(args.length - 2);
@@ -616,6 +607,16 @@ describe('autocomplete', () => {
'any',
{
evalMath: true,
+ grouping: false,
+ },
+ undefined,
+ undefined,
+ 'by'
+ );
+ const allGroupingFunctions = getFunctionSignaturesByReturnType(
+ 'stats',
+ 'any',
+ {
grouping: true,
},
undefined,
@@ -623,25 +624,26 @@ describe('autocomplete', () => {
'by'
);
testSuggestions('from a | stats ', ['var0 =', ...allAggFunctions, ...allEvaFunctions]);
- testSuggestions('from a | stats a ', [
- { text: '= $0', asSnippet: true, command: TRIGGER_SUGGESTION_COMMAND },
- ]);
+ testSuggestions('from a | stats a ', ['= $0']);
testSuggestions('from a | stats a=', [...allAggFunctions, ...allEvaFunctions]);
- testSuggestions.only('from a | stats a=max(b) by ', [
+ testSuggestions('from a | stats a=max(b) by ', [
'var0 =',
...getFieldNamesByType('any'),
...allEvaFunctions,
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats a=max(b) BY ', [
'var0 =',
...getFieldNamesByType('any'),
...allEvaFunctions,
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats a=c by d ', [',', '|']);
testSuggestions('from a | stats a=c by d, ', [
'var0 =',
...getFieldNamesByType('any'),
...allEvaFunctions,
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats a=max(b), ', [
'var0 =',
@@ -663,6 +665,7 @@ describe('autocomplete', () => {
'var0 =',
...getFieldNamesByType('any'),
...allEvaFunctions,
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats a=min(b),', ['var0 =', ...allAggFunctions, ...allEvaFunctions]);
testSuggestions('from a | stats var0=min(b),var1=c,', [
@@ -705,19 +708,23 @@ describe('autocomplete', () => {
...getFieldNamesByType('number'),
'`avg(b)`',
...getFunctionSignaturesByReturnType('eval', 'number', { evalMath: true }),
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats avg(b) by var0 = ', [
...getFieldNamesByType('any'),
...allEvaFunctions,
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats avg(b) by c, ', [
'var0 =',
...getFieldNamesByType('any'),
...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }),
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats avg(b) by c, var0 = ', [
...getFieldNamesByType('any'),
...allEvaFunctions,
+ ...allGroupingFunctions,
]);
testSuggestions('from a | stats avg(b) by numberField % 2 ', [',', '|']);
@@ -1160,7 +1167,7 @@ describe('autocomplete', () => {
}
}
- testSuggestions('from a | eval var0 = bucket(@timestamp,', []);
+ testSuggestions('from a | eval var0 = bucket(@timestamp,', getUnitDuration(1));
describe('date math', () => {
const dateSuggestions = timeLiterals.map(({ name }) => name);
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts
index 7b28ae5add262c..481399edfb4d53 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts
@@ -289,7 +289,7 @@ export const buildNoPoliciesAvailableDefinition = (): SuggestionRawDefinition =>
},
});
-function getUnitDuration(unit: number = 1) {
+export function getUnitDuration(unit: number = 1) {
const filteredTimeLiteral = timeLiterals.filter(({ name }) => {
const result = /s$/.test(name);
return unit > 1 ? result : !result;
@@ -297,6 +297,19 @@ function getUnitDuration(unit: number = 1) {
return filteredTimeLiteral.map(({ name }) => `${unit} ${name}`);
}
+/**
+ * Given information about the current command and the parameter type, suggest
+ * some literals that may make sense.
+ *
+ * TODO — this currently tries to cover both command-specific suggestions and type
+ * suggestions. We could consider separating the two... or just using parameter types
+ * and forgetting about command-specific suggestions altogether.
+ *
+ * Another thought... should literal suggestions be defined in the definitions file?
+ * That approach might allow for greater specificity in the suggestions and remove some
+ * "magical" logic. Maybe this is really the same thing as the literalOptions parameter
+ * definition property...
+ */
export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) {
const suggestions: SuggestionRawDefinition[] = [];
if (types.includes('number')) {
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
index dc37f5b2fd1ff6..73a30e5833aeee 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
@@ -362,6 +362,10 @@ describe('validation logic', () => {
type TestArgs = [string, string[], string[]?];
// Make only and skip work with our custom wrapper
+ //
+ // DO NOT CHANGE THE NAME OF THIS FUNCTION WITHOUT ALSO CHANGING
+ // THE LINTER RULE IN packages/kbn-eslint-config/typescript.js
+ //
const testErrorsAndWarnings = Object.assign(testErrorsAndWarningsFn, {
skip: (...args: TestArgs) => {
const warningArgs = [[]].slice(args.length - 2);
diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts
index 965ee67355c580..6af0e391f2308e 100644
--- a/src/plugins/dashboard/public/dashboard_constants.ts
+++ b/src/plugins/dashboard/public/dashboard_constants.ts
@@ -65,6 +65,13 @@ export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2;
export const CHANGE_CHECK_DEBOUNCE = 100;
+export enum PanelPlacementStrategy {
+ /** Place on the very top of the Dashboard, add the height of this panel to all other panels. */
+ placeAtTop = 'placeAtTop',
+ /** Look for the smallest y and x value where the default panel will fit. */
+ findTopLeftMostOpenSpace = 'findTopLeftMostOpenSpace',
+}
+
// ------------------------------------------------------------------
// Content Management
// ------------------------------------------------------------------
diff --git a/src/plugins/dashboard/public/dashboard_container/_dashboard_container_strings.ts b/src/plugins/dashboard/public/dashboard_container/_dashboard_container_strings.ts
index 8bccc6ce4f5a9e..399f3c6128e3d2 100644
--- a/src/plugins/dashboard/public/dashboard_container/_dashboard_container_strings.ts
+++ b/src/plugins/dashboard/public/dashboard_container/_dashboard_container_strings.ts
@@ -105,3 +105,16 @@ export const backupServiceStrings = {
values: { message },
}),
};
+
+export const panelPlacementStrings = {
+ getUnknownStrategyError: (strategy: string) =>
+ i18n.translate('dashboard.panelPlacement.unknownStrategyError', {
+ defaultMessage: 'Unknown panel placement strategy: {strategy}',
+ values: { strategy },
+ }),
+ getPanelPlacementSettingsExistsError: (panelType: string) =>
+ i18n.translate('dashboard.panelPlacement.panelPlacementSettingsExistsError', {
+ defaultMessage: 'Panel placement settings for embeddable type {panelType} already exists',
+ values: { panelType },
+ }),
+};
diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts
index 8a8c8a83193ebf..168dcfa72a491a 100644
--- a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts
+++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_new_panel_strategies.ts
@@ -7,98 +7,98 @@
*/
import { cloneDeep } from 'lodash';
-import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants';
+import { DASHBOARD_GRID_COLUMN_COUNT, PanelPlacementStrategy } from '../../../dashboard_constants';
+import { panelPlacementStrings } from '../../_dashboard_container_strings';
import { PanelPlacementProps, PanelPlacementReturn } from './types';
-export const panelPlacementStrategies = {
- // Place on the very top of the Dashboard, add the height of this panel to all other panels.
- placeAtTop: ({ width, height, currentPanels }: PanelPlacementProps): PanelPlacementReturn => {
- const otherPanels = { ...currentPanels };
- for (const [id, panel] of Object.entries(currentPanels)) {
- const currentPanel = cloneDeep(panel);
- currentPanel.gridData.y = currentPanel.gridData.y + height;
- otherPanels[id] = currentPanel;
- }
- return {
- newPanelPlacement: { x: 0, y: 0, w: width, h: height },
- otherPanels,
- };
- },
-
- // Look for the smallest y and x value where the default panel will fit.
- findTopLeftMostOpenSpace: ({
- width,
- height,
- currentPanels,
- }: PanelPlacementProps): PanelPlacementReturn => {
- let maxY = -1;
-
- const currentPanelsArray = Object.values(currentPanels);
- currentPanelsArray.forEach((panel) => {
- maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY);
- });
-
- // Handle case of empty grid.
- if (maxY < 0) {
+export const runPanelPlacementStrategy = (
+ strategy: PanelPlacementStrategy,
+ { width, height, currentPanels }: PanelPlacementProps
+): PanelPlacementReturn => {
+ switch (strategy) {
+ case PanelPlacementStrategy.placeAtTop:
+ const otherPanels = { ...currentPanels };
+ for (const [id, panel] of Object.entries(currentPanels)) {
+ const currentPanel = cloneDeep(panel);
+ currentPanel.gridData.y = currentPanel.gridData.y + height;
+ otherPanels[id] = currentPanel;
+ }
return {
newPanelPlacement: { x: 0, y: 0, w: width, h: height },
- otherPanels: currentPanels,
+ otherPanels,
};
- }
- const grid = new Array(maxY);
- for (let y = 0; y < maxY; y++) {
- grid[y] = new Array(DASHBOARD_GRID_COLUMN_COUNT).fill(0);
- }
+ case PanelPlacementStrategy.findTopLeftMostOpenSpace:
+ let maxY = -1;
+
+ const currentPanelsArray = Object.values(currentPanels);
+ currentPanelsArray.forEach((panel) => {
+ maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY);
+ });
+
+ // Handle case of empty grid.
+ if (maxY < 0) {
+ return {
+ newPanelPlacement: { x: 0, y: 0, w: width, h: height },
+ otherPanels: currentPanels,
+ };
+ }
- currentPanelsArray.forEach((panel) => {
- for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) {
- for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) {
- const row = grid[y];
- if (row === undefined) {
- throw new Error(
- `Attempted to access a row that doesn't exist at ${y} for panel ${JSON.stringify(
- panel
- )}`
- );
+ const grid = new Array(maxY);
+ for (let y = 0; y < maxY; y++) {
+ grid[y] = new Array(DASHBOARD_GRID_COLUMN_COUNT).fill(0);
+ }
+
+ currentPanelsArray.forEach((panel) => {
+ for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) {
+ for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) {
+ const row = grid[y];
+ if (row === undefined) {
+ throw new Error(
+ `Attempted to access a row that doesn't exist at ${y} for panel ${JSON.stringify(
+ panel
+ )}`
+ );
+ }
+ grid[y][x] = 1;
}
- grid[y][x] = 1;
}
- }
- });
+ });
- for (let y = 0; y < maxY; y++) {
- for (let x = 0; x < DASHBOARD_GRID_COLUMN_COUNT; x++) {
- if (grid[y][x] === 1) {
- // Space is filled
- continue;
- } else {
- for (let h = y; h < Math.min(y + height, maxY); h++) {
- for (let w = x; w < Math.min(x + width, DASHBOARD_GRID_COLUMN_COUNT); w++) {
- const spaceIsEmpty = grid[h][w] === 0;
- const fitsPanelWidth = w === x + width - 1;
- // If the panel is taller than any other panel in the current grid, it can still fit in the space, hence
- // we check the minimum of maxY and the panel height.
- const fitsPanelHeight = h === Math.min(y + height - 1, maxY - 1);
+ for (let y = 0; y < maxY; y++) {
+ for (let x = 0; x < DASHBOARD_GRID_COLUMN_COUNT; x++) {
+ if (grid[y][x] === 1) {
+ // Space is filled
+ continue;
+ } else {
+ for (let h = y; h < Math.min(y + height, maxY); h++) {
+ for (let w = x; w < Math.min(x + width, DASHBOARD_GRID_COLUMN_COUNT); w++) {
+ const spaceIsEmpty = grid[h][w] === 0;
+ const fitsPanelWidth = w === x + width - 1;
+ // If the panel is taller than any other panel in the current grid, it can still fit in the space, hence
+ // we check the minimum of maxY and the panel height.
+ const fitsPanelHeight = h === Math.min(y + height - 1, maxY - 1);
- if (spaceIsEmpty && fitsPanelWidth && fitsPanelHeight) {
- // Found space
- return {
- newPanelPlacement: { x, y, w: width, h: height },
- otherPanels: currentPanels,
- };
- } else if (grid[h][w] === 1) {
- // x, y spot doesn't work, break.
- break;
+ if (spaceIsEmpty && fitsPanelWidth && fitsPanelHeight) {
+ // Found space
+ return {
+ newPanelPlacement: { x, y, w: width, h: height },
+ otherPanels: currentPanels,
+ };
+ } else if (grid[h][w] === 1) {
+ // x, y spot doesn't work, break.
+ break;
+ }
}
}
}
}
}
- }
- return {
- newPanelPlacement: { x: 0, y: maxY, w: width, h: height },
- otherPanels: currentPanels,
- };
- },
-} as const;
+ return {
+ newPanelPlacement: { x: 0, y: maxY, w: width, h: height },
+ otherPanels: currentPanels,
+ };
+ default:
+ throw new Error(panelPlacementStrings.getUnknownStrategyError(strategy));
+ }
+};
diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts
index a65c4fca9c1158..c440c0fe93e103 100644
--- a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts
+++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/place_panel.ts
@@ -9,9 +9,13 @@
import { PanelState, EmbeddableInput, EmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { DashboardPanelState } from '../../../../common';
-import { panelPlacementStrategies } from './place_new_panel_strategies';
-import { IProvidesPanelPlacementSettings, PanelPlacementSettings } from './types';
-import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../dashboard_constants';
+import { IProvidesPanelPlacementSettings } from './types';
+import { runPanelPlacementStrategy } from './place_new_panel_strategies';
+import {
+ DEFAULT_PANEL_HEIGHT,
+ DEFAULT_PANEL_WIDTH,
+ PanelPlacementStrategy,
+} from '../../../dashboard_constants';
export const providesPanelPlacementSettings = (
value: unknown
@@ -28,10 +32,10 @@ export function placePanel(
newPanel: DashboardPanelState;
otherPanels: { [key: string]: DashboardPanelState };
} {
- let placementSettings: PanelPlacementSettings = {
+ let placementSettings = {
width: DEFAULT_PANEL_WIDTH,
height: DEFAULT_PANEL_HEIGHT,
- strategy: 'findTopLeftMostOpenSpace',
+ strategy: PanelPlacementStrategy.findTopLeftMostOpenSpace,
};
if (providesPanelPlacementSettings(factory)) {
placementSettings = {
@@ -41,7 +45,7 @@ export function placePanel(
}
const { width, height, strategy } = placementSettings;
- const { newPanelPlacement, otherPanels } = panelPlacementStrategies[strategy]({
+ const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(strategy, {
currentPanels,
height,
width,
diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts
index 7fb20b469c1a92..d5fdbf705f4431 100644
--- a/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts
+++ b/src/plugins/dashboard/public/dashboard_container/component/panel_placement/types.ts
@@ -9,14 +9,12 @@
import { EmbeddableInput } from '@kbn/embeddable-plugin/public';
import { DashboardPanelState } from '../../../../common';
import { GridData } from '../../../../common/content_management';
-import { panelPlacementStrategies } from './place_new_panel_strategies';
-
-export type PanelPlacementStrategy = keyof typeof panelPlacementStrategies;
+import { PanelPlacementStrategy } from '../../../dashboard_constants';
export interface PanelPlacementSettings {
- strategy: PanelPlacementStrategy;
- height: number;
- width: number;
+ strategy?: PanelPlacementStrategy;
+ height?: number;
+ width?: number;
}
export interface PanelPlacementReturn {
diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts
index 23d677b3e47c56..88ece952a49b55 100644
--- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts
+++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts
@@ -39,13 +39,14 @@ import {
DEFAULT_PANEL_HEIGHT,
DEFAULT_PANEL_WIDTH,
GLOBAL_STATE_STORAGE_KEY,
+ PanelPlacementStrategy,
} from '../../../dashboard_constants';
import {
LoadDashboardReturn,
SavedDashboardInput,
} from '../../../services/dashboard_content_management/types';
import { pluginServices } from '../../../services/plugin_services';
-import { panelPlacementStrategies } from '../../component/panel_placement/place_new_panel_strategies';
+import { runPanelPlacementStrategy } from '../../component/panel_placement/place_new_panel_strategies';
import { startDiffingDashboardState } from '../../state/diffing/dashboard_diffing_integration';
import { DashboardPublicState } from '../../types';
import { DashboardContainer } from '../dashboard_container';
@@ -353,12 +354,14 @@ export const initializeDashboard = async ({
const { width, height } = incomingEmbeddable.size;
const currentPanels = container.getInput().panels;
const embeddableId = incomingEmbeddable.embeddableId ?? v4();
- const { findTopLeftMostOpenSpace } = panelPlacementStrategies;
- const { newPanelPlacement } = findTopLeftMostOpenSpace({
- width: width ?? DEFAULT_PANEL_WIDTH,
- height: height ?? DEFAULT_PANEL_HEIGHT,
- currentPanels,
- });
+ const { newPanelPlacement } = runPanelPlacementStrategy(
+ PanelPlacementStrategy.findTopLeftMostOpenSpace,
+ {
+ width: width ?? DEFAULT_PANEL_WIDTH,
+ height: height ?? DEFAULT_PANEL_HEIGHT,
+ currentPanels,
+ }
+ );
const newPanelState: DashboardPanelState = {
explicitInput: { ...incomingEmbeddable.input, id: embeddableId },
type: incomingEmbeddable.type,
diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx
index e54753b2b85399..69fdf28647c092 100644
--- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx
@@ -57,14 +57,16 @@ import {
DASHBOARD_UI_METRIC_ID,
DEFAULT_PANEL_HEIGHT,
DEFAULT_PANEL_WIDTH,
+ PanelPlacementStrategy,
} from '../../dashboard_constants';
import { DashboardAnalyticsService } from '../../services/analytics/types';
import { DashboardCapabilitiesService } from '../../services/dashboard_capabilities/types';
import { pluginServices } from '../../services/plugin_services';
import { placePanel } from '../component/panel_placement';
-import { panelPlacementStrategies } from '../component/panel_placement/place_new_panel_strategies';
+import { runPanelPlacementStrategy } from '../component/panel_placement/place_new_panel_strategies';
import { DashboardViewport } from '../component/viewport/dashboard_viewport';
import { DashboardExternallyAccessibleApi } from '../external_api/dashboard_api';
+import { getDashboardPanelPlacementSetting } from '../external_api/dashboard_panel_placement_registry';
import { dashboardContainerReducers } from '../state/dashboard_container_reducers';
import { getDiffingMiddleware } from '../state/diffing/dashboard_diffing_integration';
import {
@@ -498,10 +500,20 @@ export class DashboardContainer
}
if (reactEmbeddableRegistryHasKey(panelPackage.panelType)) {
const newId = v4();
- const { newPanelPlacement, otherPanels } = panelPlacementStrategies.findTopLeftMostOpenSpace({
- currentPanels: this.getInput().panels,
- height: DEFAULT_PANEL_HEIGHT,
+
+ const placementSettings = {
width: DEFAULT_PANEL_WIDTH,
+ height: DEFAULT_PANEL_HEIGHT,
+ strategy: PanelPlacementStrategy.findTopLeftMostOpenSpace,
+ ...getDashboardPanelPlacementSetting(panelPackage.panelType)?.(panelPackage.initialState),
+ };
+
+ const { width, height, strategy } = placementSettings;
+
+ const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(strategy, {
+ currentPanels: this.getInput().panels,
+ height,
+ width,
});
const newPanel: DashboardPanelState = {
type: panelPackage.panelType,
diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_panel_placement_registry.ts b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_panel_placement_registry.ts
new file mode 100644
index 00000000000000..e21fedb9aabe1e
--- /dev/null
+++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_panel_placement_registry.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { PanelPlacementSettings } from '../component/panel_placement/types';
+import { panelPlacementStrings } from '../_dashboard_container_strings';
+
+type GetPanelPlacementSettings = (
+ serializedState?: SerializedState
+) => PanelPlacementSettings;
+
+const registry = new Map>();
+
+export const registerDashboardPanelPlacementSetting = (
+ embeddableType: string,
+ getPanelPlacementSettings: GetPanelPlacementSettings
+) => {
+ if (registry.has(embeddableType)) {
+ throw new Error(panelPlacementStrings.getPanelPlacementSettingsExistsError(embeddableType));
+ }
+ registry.set(embeddableType, getPanelPlacementSettings);
+};
+
+export const getDashboardPanelPlacementSetting = (embeddableType: string) => {
+ return registry.get(embeddableType);
+};
diff --git a/src/plugins/dashboard/public/dashboard_container/index.ts b/src/plugins/dashboard/public/dashboard_container/index.ts
index e0b421d65fd156..c8944968e6cb7c 100644
--- a/src/plugins/dashboard/public/dashboard_container/index.ts
+++ b/src/plugins/dashboard/public/dashboard_container/index.ts
@@ -22,4 +22,5 @@ export {
export { DashboardRenderer } from './external_api/dashboard_renderer';
export type { DashboardAPI, AwaitingDashboardAPI } from './external_api/dashboard_api';
+export { registerDashboardPanelPlacementSetting } from './external_api/dashboard_panel_placement_registry';
export type { DashboardLocatorParams } from './types';
diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts
index 03cd4e03d52a51..3b1bafe2e1fd47 100644
--- a/src/plugins/dashboard/public/index.ts
+++ b/src/plugins/dashboard/public/index.ts
@@ -14,8 +14,10 @@ export {
DASHBOARD_APP_ID,
LEGACY_DASHBOARD_APP_ID,
DASHBOARD_GRID_COLUMN_COUNT,
+ PanelPlacementStrategy,
} from './dashboard_constants';
export {
+ registerDashboardPanelPlacementSetting,
type DashboardAPI,
type AwaitingDashboardAPI,
DashboardRenderer,
diff --git a/src/plugins/links/public/embeddable/links_embeddable_factory.ts b/src/plugins/links/public/embeddable/links_embeddable_factory.ts
index fa489ff4ee9777..df629c9b1d0534 100644
--- a/src/plugins/links/public/embeddable/links_embeddable_factory.ts
+++ b/src/plugins/links/public/embeddable/links_embeddable_factory.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { DASHBOARD_GRID_COLUMN_COUNT } from '@kbn/dashboard-plugin/public';
+import { DASHBOARD_GRID_COLUMN_COUNT, PanelPlacementStrategy } from '@kbn/dashboard-plugin/public';
import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container';
import { IProvidesPanelPlacementSettings } from '@kbn/dashboard-plugin/public/dashboard_container/component/panel_placement/types';
import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common';
@@ -76,7 +76,7 @@ export class LinksFactoryDefinition
const isHorizontal = attributes.layout === 'horizontal';
const width = isHorizontal ? DASHBOARD_GRID_COLUMN_COUNT : 8;
const height = isHorizontal ? 4 : (attributes.links?.length ?? 1 * 3) + 4;
- return { width, height, strategy: 'placeAtTop' };
+ return { width, height, strategy: PanelPlacementStrategy.placeAtTop };
};
public async isEditable() {
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
index c205ef7f825258..ae1a1cb755afc9 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
@@ -549,7 +549,7 @@ export const CspPolicyTemplateForm = memo
)}
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts
index 1eb3334d09103f..eb18a90b72936d 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts
@@ -38,8 +38,15 @@ export const useSetupTechnology = ({
return SetupTechnology.AGENT_BASED;
});
+ const [isDirty, setIsDirty] = useState(false);
+
+ const updateSetupTechnology = (value: SetupTechnology) => {
+ setSetupTechnology(value);
+ setIsDirty(true);
+ };
+
useEffect(() => {
- if (isEditPage) {
+ if (isEditPage || isDirty) {
return;
}
@@ -58,7 +65,7 @@ export const useSetupTechnology = ({
} else {
setSetupTechnology(SetupTechnology.AGENT_BASED);
}
- }, [agentPolicyId, agentlessPolicyId, isAgentlessAvailable, isEditPage]);
+ }, [agentPolicyId, agentlessPolicyId, isAgentlessAvailable, isDirty, isEditPage]);
useEffect(() => {
if (isEditPage) {
@@ -74,5 +81,6 @@ export const useSetupTechnology = ({
isAgentlessAvailable,
setupTechnology,
setSetupTechnology,
+ updateSetupTechnology,
};
};
diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts
index 94353b7ee35653..744369ca188068 100644
--- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts
@@ -30,7 +30,7 @@ export const createIndexPipelineDefinitions = async (
let result: Record = {};
try {
const mlPipeline = {
- description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`,
+ description: `Machine Learning Inference pipeline for the '${indexName}' index`,
id: getInferencePipelineNameFromIndexName(indexName),
processors: [],
version: 1,
@@ -38,7 +38,7 @@ export const createIndexPipelineDefinitions = async (
await esClient.ingest.putPipeline(mlPipeline);
result = { ...result, [mlPipeline.id]: mlPipeline };
const customPipeline = {
- description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`,
+ description: `Customizable ingest pipeline for the '${indexName}' index`,
id: `${indexName}@custom`,
processors: [],
version: 1,
@@ -48,9 +48,9 @@ export const createIndexPipelineDefinitions = async (
const ingestPipeline = {
_meta: {
managed: true,
- managed_by: 'Enterprise Search',
+ managed_by: 'Search',
},
- description: `Enterprise Search ingest pipeline for the '${indexName}' index`,
+ description: `Ingest pipeline for the '${indexName}' index`,
id: `${indexName}`,
processors: [
{
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts
index 7088bdd37f0439..2eab867e2ae121 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts
@@ -37,7 +37,7 @@ export function useSetupTechnology({
}: {
updateNewAgentPolicy: (policy: NewAgentPolicy) => void;
newAgentPolicy: NewAgentPolicy;
- updateAgentPolicy: (policy: AgentPolicy) => void;
+ updateAgentPolicy: (policy: AgentPolicy | undefined) => void;
setSelectedPolicyTab: (tab: SelectedPolicyTab) => void;
}) {
const { isAgentlessEnabled } = useAgentlessPolicy();
@@ -75,8 +75,8 @@ export function useSetupTechnology({
} else if (setupTechnology === SetupTechnology.AGENT_BASED) {
updateNewAgentPolicy(newAgentPolicy);
setSelectedPolicyTab(SelectedPolicyTab.NEW);
+ updateAgentPolicy(undefined);
}
-
setSelectedSetupTechnology(setupTechnology);
},
[
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index 6093f7b327b17d..572b7a4136ea54 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -226,10 +226,15 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
}
};
+ if (selectedPolicyTab === SelectedPolicyTab.NEW) {
+ setAgentCount(0);
+ return;
+ }
+
if (isFleetEnabled && agentPolicyId) {
getAgentCount();
}
- }, [agentPolicyId, isFleetEnabled]);
+ }, [agentPolicyId, selectedPolicyTab, isFleetEnabled]);
const handleExtensionViewOnChange = useCallback<
PackagePolicyEditExtensionComponentProps['onChange']
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.test.ts
index 2ad57f32f9cf9e..a5b5ac8424597d 100644
--- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.test.ts
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.test.ts
@@ -85,8 +85,31 @@ describe('8.14.0 Endpoint Package Policy migration', () => {
});
describe('backfilling `on_write_scan`', () => {
- it('should backfill `on_write_scan` field to malware protections on Kibana update', () => {
+ it('should backfill `on_write_scan` field as `true` for `malware prevent` on Kibana update', () => {
const originalPolicyConfigSO = cloneDeep(policyDoc);
+ const originalPolicyConfig = originalPolicyConfigSO.attributes.inputs[0].config?.policy.value;
+ originalPolicyConfig.windows.malware.mode = 'prevent';
+ originalPolicyConfig.mac.malware.mode = 'prevent';
+ originalPolicyConfig.linux.malware.mode = 'prevent';
+
+ const migratedPolicyConfigSO = migrator.migrate({
+ document: originalPolicyConfigSO,
+ fromVersion: 5,
+ toVersion: 6,
+ });
+
+ const migratedPolicyConfig = migratedPolicyConfigSO.attributes.inputs[0].config?.policy.value;
+ expect(migratedPolicyConfig.windows.malware.on_write_scan).toBe(true);
+ expect(migratedPolicyConfig.mac.malware.on_write_scan).toBe(true);
+ expect(migratedPolicyConfig.linux.malware.on_write_scan).toBe(true);
+ });
+
+ it('should backfill `on_write_scan` field as `true` for `malware detect` on Kibana update', () => {
+ const originalPolicyConfigSO = cloneDeep(policyDoc);
+ const originalPolicyConfig = originalPolicyConfigSO.attributes.inputs[0].config?.policy.value;
+ originalPolicyConfig.windows.malware.mode = 'detect';
+ originalPolicyConfig.mac.malware.mode = 'detect';
+ originalPolicyConfig.linux.malware.mode = 'detect';
const migratedPolicyConfigSO = migrator.migrate({
document: originalPolicyConfigSO,
@@ -100,6 +123,25 @@ describe('8.14.0 Endpoint Package Policy migration', () => {
expect(migratedPolicyConfig.linux.malware.on_write_scan).toBe(true);
});
+ it('should backfill `on_write_scan` field as `false` for `malware off` on Kibana update', () => {
+ const originalPolicyConfigSO = cloneDeep(policyDoc);
+ const originalPolicyConfig = originalPolicyConfigSO.attributes.inputs[0].config?.policy.value;
+ originalPolicyConfig.windows.malware.mode = 'off';
+ originalPolicyConfig.mac.malware.mode = 'off';
+ originalPolicyConfig.linux.malware.mode = 'off';
+
+ const migratedPolicyConfigSO = migrator.migrate({
+ document: originalPolicyConfigSO,
+ fromVersion: 5,
+ toVersion: 6,
+ });
+
+ const migratedPolicyConfig = migratedPolicyConfigSO.attributes.inputs[0].config?.policy.value;
+ expect(migratedPolicyConfig.windows.malware.on_write_scan).toBe(false);
+ expect(migratedPolicyConfig.mac.malware.on_write_scan).toBe(false);
+ expect(migratedPolicyConfig.linux.malware.on_write_scan).toBe(false);
+ });
+
it('should not backfill `on_write_scan` field if already present due to user edit before migration is performed on serverless', () => {
const originalPolicyConfigSO = cloneDeep(policyDoc);
const originalPolicyConfig = originalPolicyConfigSO.attributes.inputs[0].config?.policy.value;
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.ts
index c94332d22afa8f..89a0715900ebe3 100644
--- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.ts
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_14_0.ts
@@ -28,9 +28,12 @@ export const migratePackagePolicyToV8140: SavedObjectModelDataBackfillFn<
if (input && input.config) {
const policy = input.config.policy.value;
- policy.windows.malware.on_write_scan ??= ON_WRITE_SCAN_DEFAULT_VALUE;
- policy.mac.malware.on_write_scan ??= ON_WRITE_SCAN_DEFAULT_VALUE;
- policy.linux.malware.on_write_scan ??= ON_WRITE_SCAN_DEFAULT_VALUE;
+ policy.windows.malware.on_write_scan ??=
+ policy.windows.malware.mode === 'off' ? false : ON_WRITE_SCAN_DEFAULT_VALUE;
+ policy.mac.malware.on_write_scan ??=
+ policy.mac.malware.mode === 'off' ? false : ON_WRITE_SCAN_DEFAULT_VALUE;
+ policy.linux.malware.on_write_scan ??=
+ policy.linux.malware.mode === 'off' ? false : ON_WRITE_SCAN_DEFAULT_VALUE;
}
return { attributes: updatedPackagePolicyDoc.attributes };
diff --git a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts
index 912bc637120be4..a97f578cfadf88 100644
--- a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts
+++ b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts
@@ -36,16 +36,18 @@ spec:
# Uncomment if using hints feature
#initContainers:
# - name: k8s-templates-downloader
- # image: busybox:1.28
- # command: ['sh']
+ # image: docker.elastic.co/beats/elastic-agent:VERSION
+ # command: ['bash']
# args:
# - -c
# - >-
- # mkdir -p /etc/elastic-agent/inputs.d &&
- # wget -O - https://github.com/elastic/elastic-agent/archive/8.15.tar.gz | tar xz -C /etc/elastic-agent/inputs.d --strip=5 "elastic-agent-8.15/deploy/kubernetes/elastic-agent/templates.d"
+ # mkdir -p /usr/share/elastic-agent/state/inputs.d &&
+ # curl -sL https://github.com/elastic/elastic-agent/archive/8.15.tar.gz | tar xz -C /usr/share/elastic-agent/state/inputs.d --strip=5 "elastic-agent-8.15/deploy/kubernetes/elastic-agent/templates.d"
+ # securityContext:
+ # runAsUser: 0
# volumeMounts:
- # - name: external-inputs
- # mountPath: /etc/elastic-agent/inputs.d
+ # - name: elastic-agent-state
+ # mountPath: /usr/share/elastic-agent/state
containers:
- name: elastic-agent
image: docker.elastic.co/beats/elastic-agent:VERSION
@@ -66,8 +68,6 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- - name: STATE_PATH
- value: "/etc/elastic-agent"
# The following ELASTIC_NETINFO:false variable will disable the netinfo.enabled option of add-host-metadata processor. This will remove fields host.ip and host.mac.
# For more info: https://www.elastic.co/guide/en/beats/metricbeat/current/add-host-metadata.html
- name: ELASTIC_NETINFO
@@ -101,9 +101,6 @@ spec:
mountPath: /etc/elastic-agent/agent.yml
readOnly: true
subPath: agent.yml
- # Uncomment if using hints feature
- #- name: external-inputs
- # mountPath: /etc/elastic-agent/inputs.d
- name: proc
mountPath: /hostfs/proc
readOnly: true
@@ -134,9 +131,6 @@ spec:
configMap:
defaultMode: 0640
name: agent-node-datastreams
- # Uncomment if using hints feature
- #- name: external-inputs
- # emptyDir: {}
- name: proc
hostPath:
path: /proc
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts
index c816e9a5ed2e37..5a15772d5e16ab 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts
@@ -41,6 +41,8 @@ export interface IndexDetailsPageTestBed extends TestBed {
getActiveTabContent: () => string;
mappings: {
addNewMappingFieldNameAndType: (mappingFields?: MappingField[]) => Promise;
+ clickFilterByFieldType: () => Promise;
+ selectFilterFieldType: (fieldType: string) => Promise;
clickAddFieldButton: () => Promise;
clickSaveMappingsButton: () => Promise;
getCodeBlockContent: () => string;
@@ -51,6 +53,8 @@ export interface IndexDetailsPageTestBed extends TestBed {
getTreeViewContent: (fieldName: string) => string;
clickToggleViewButton: () => Promise;
isSearchBarDisabled: () => boolean;
+ setSearchBarValue: (searchValue: string) => Promise;
+ findSearchResult: () => string;
isSemanticTextBannerVisible: () => boolean;
};
settings: {
@@ -216,9 +220,35 @@ export const setup = async ({
});
component.update();
},
+ clickFilterByFieldType: async () => {
+ expect(exists('indexDetailsMappingsFilterByFieldTypeButton')).toBe(true);
+ await act(async () => {
+ find('indexDetailsMappingsFilterByFieldTypeButton').simulate('click');
+ });
+ component.update();
+ },
+ selectFilterFieldType: async (fieldType: string) => {
+ expect(testBed.exists('indexDetailsMappingsSelectFilter-text')).toBe(true);
+ await act(async () => {
+ find(fieldType).simulate('click');
+ });
+ component.update();
+ },
isSearchBarDisabled: () => {
return find('indexDetailsMappingsFieldSearch').prop('disabled');
},
+ setSearchBarValue: async (searchValue: string) => {
+ await act(async () => {
+ testBed
+ .find('indexDetailsMappingsFieldSearch')
+ .simulate('change', { target: { value: searchValue } });
+ });
+ component.update();
+ },
+ findSearchResult: () => {
+ expect(testBed.exists('fieldName')).toBe(true);
+ return testBed.find('fieldName').text();
+ },
isSemanticTextBannerVisible: () => {
return exists('indexDetailsMappingsSemanticTextBanner');
},
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx
index d5cb6f90e8b43a..37e2799678e79b 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx
@@ -471,10 +471,11 @@ describe(' ', () => {
requestOptions
);
});
- it('searchbar, toggle button, add field button exists', async () => {
+ it('filter, searchbar, toggle button, add field button exists', async () => {
expect(testBed.exists('indexDetailsMappingsAddField')).toBe(true);
expect(testBed.exists('indexDetailsMappingsToggleViewButton')).toBe(true);
expect(testBed.exists('indexDetailsMappingsFieldSearch')).toBe(true);
+ expect(testBed.exists('indexDetailsMappingsFilter')).toBe(true);
});
it('displays the mappings in the table view', async () => {
@@ -508,6 +509,57 @@ describe(' ', () => {
'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/mapping.html'
);
});
+ describe('Filter field by filter Type', () => {
+ const mockIndexMappingResponse: any = {
+ ...testIndexMappings.mappings,
+ properties: {
+ ...testIndexMappings.mappings.properties,
+ name: {
+ type: 'text',
+ },
+ },
+ };
+ beforeEach(async () => {
+ httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, {
+ mappings: mockIndexMappingResponse,
+ });
+ await act(async () => {
+ testBed = await setup({ httpSetup });
+ });
+ testBed.component.update();
+ await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings);
+ });
+ test('popover is visible and shows list of available field types', async () => {
+ await testBed.actions.mappings.clickFilterByFieldType();
+ expect(testBed.exists('euiSelectableList')).toBe(true);
+ expect(testBed.exists('indexDetailsMappingsFilterByFieldTypeSearch')).toBe(true);
+ expect(testBed.exists('euiSelectableList')).toBe(true);
+ });
+ test('can select a field type and list view changes', async () => {
+ await testBed.actions.mappings.clickFilterByFieldType();
+ await testBed.actions.mappings.selectFilterFieldType(
+ 'indexDetailsMappingsSelectFilter-text'
+ );
+ expect(testBed.actions.mappings.getTreeViewContent('nameField-fieldName')).toContain(
+ 'name'
+ );
+ expect(testBed.find('@timestampField-fieldName')).not.toContain('@timestamp');
+ });
+ test('can search field with filter', async () => {
+ expect(testBed.find('fieldName')).toHaveLength(2);
+
+ // set filter
+ await testBed.actions.mappings.clickFilterByFieldType();
+ await testBed.actions.mappings.selectFilterFieldType(
+ 'indexDetailsMappingsSelectFilter-text'
+ );
+
+ await testBed.actions.mappings.setSearchBarValue('na');
+ expect(testBed.find('fieldName')).toHaveLength(1);
+ expect(testBed.actions.mappings.findSearchResult()).not.toBe('@timestamp');
+ expect(testBed.actions.mappings.findSearchResult()).toBe('name');
+ });
+ });
describe('Add a new field ', () => {
const mockIndexMappingResponse: any = {
...testIndexMappings.mappings,
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result.tsx
index e61bcd86399197..2adcf441749c26 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result.tsx
@@ -18,6 +18,7 @@ interface Props {
result: SearchResultType[];
documentFieldsState: State['documentFields'];
style?: React.CSSProperties;
+ onClearSearch?: () => void;
}
const ITEM_HEIGHT = 64;
@@ -50,12 +51,21 @@ const Row = React.memo(({ data, index, style }) => {
}, areEqual);
export const SearchResult = React.memo(
- ({ result, documentFieldsState: { status, fieldToEdit }, style: virtualListStyle }: Props) => {
+ ({
+ result,
+ documentFieldsState: { status, fieldToEdit },
+ style: virtualListStyle,
+ onClearSearch,
+ }: Props) => {
const dispatch = useDispatch();
const listHeight = Math.min(result.length * ITEM_HEIGHT, 600);
const clearSearch = () => {
- dispatch({ type: 'search:update', value: '' });
+ if (onClearSearch !== undefined) {
+ onClearSearch();
+ } else {
+ dispatch({ type: 'search:update', value: '' });
+ }
};
const itemData = useMemo(
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts
index 1619271d652c5d..c14c408fee24ec 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts
@@ -27,6 +27,9 @@ export {
stripUndefinedValues,
normalizeRuntimeFields,
deNormalizeRuntimeFields,
+ getAllFieldTypesFromState,
+ getFieldsFromState,
+ getFieldsMatchingFilterFromState,
} from './utils';
export * from './serializers';
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts
index 1f1ca1b34deef2..be00b71b58ea12 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts
@@ -10,7 +10,143 @@ jest.mock('../constants', () => {
return { MAIN_DATA_TYPE_DEFINITION: {}, TYPE_DEFINITION };
});
-import { stripUndefinedValues, getTypeLabelFromField, getFieldMeta } from './utils';
+import { Fields, NormalizedFields, State } from '../types';
+import {
+ stripUndefinedValues,
+ getTypeLabelFromField,
+ getFieldMeta,
+ getFieldsFromState,
+ getAllFieldTypesFromState,
+ getFieldsMatchingFilterFromState,
+} from './utils';
+
+const fieldsWithnestedFields: NormalizedFields = {
+ byId: {
+ '4459e8f2-3bec-4d17-b50c-f62d1fcbcca0': {
+ id: '4459e8f2-3bec-4d17-b50c-f62d1fcbcca0',
+ parentId: 'dd0dd3aa-52c9-472b-a23d-ecec0a1b2420',
+ nestedDepth: 1,
+ isMultiField: false,
+ path: ['multifield', 'flag'],
+ source: {
+ name: 'flag',
+ type: 'boolean',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ '20fffee6-2a94-4aa6-b7e4-1cd745d6f775': {
+ id: '20fffee6-2a94-4aa6-b7e4-1cd745d6f775',
+ parentId: '97399281-b0b2-4490-9931-6cc92676b305',
+ nestedDepth: 3,
+ isMultiField: false,
+ path: ['multifield', 'points', 'entity', 'entity_1'],
+ source: {
+ name: 'entity_1',
+ type: 'keyword',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ '97399281-b0b2-4490-9931-6cc92676b305': {
+ id: '97399281-b0b2-4490-9931-6cc92676b305',
+ parentId: '9031c735-a445-491f-948d-41989b51a1a3',
+ nestedDepth: 2,
+ isMultiField: false,
+ path: ['multifield', 'points', 'entity'],
+ source: {
+ name: 'entity',
+ type: 'object',
+ },
+ childFieldsName: 'properties',
+ canHaveChildFields: true,
+ hasChildFields: true,
+ canHaveMultiFields: false,
+ hasMultiFields: false,
+ isExpanded: false,
+ childFields: ['20fffee6-2a94-4aa6-b7e4-1cd745d6f775'],
+ },
+ 'af9b7a29-8c44-4dbe-baa0-c29eb1760d96': {
+ id: 'af9b7a29-8c44-4dbe-baa0-c29eb1760d96',
+ parentId: '9031c735-a445-491f-948d-41989b51a1a3',
+ nestedDepth: 2,
+ isMultiField: false,
+ path: ['multifield', 'points', 'name'],
+ source: {
+ name: 'name',
+ type: 'text',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ '9031c735-a445-491f-948d-41989b51a1a3': {
+ id: '9031c735-a445-491f-948d-41989b51a1a3',
+ parentId: 'dd0dd3aa-52c9-472b-a23d-ecec0a1b2420',
+ nestedDepth: 1,
+ isMultiField: false,
+ path: ['multifield', 'points'],
+ source: {
+ name: 'points',
+ type: 'object',
+ },
+ childFieldsName: 'properties',
+ canHaveChildFields: true,
+ hasChildFields: true,
+ canHaveMultiFields: false,
+ hasMultiFields: false,
+ isExpanded: false,
+ childFields: ['97399281-b0b2-4490-9931-6cc92676b305', 'af9b7a29-8c44-4dbe-baa0-c29eb1760d96'],
+ },
+ 'dd0dd3aa-52c9-472b-a23d-ecec0a1b2420': {
+ id: 'dd0dd3aa-52c9-472b-a23d-ecec0a1b2420',
+ nestedDepth: 0,
+ isMultiField: false,
+ path: ['multifield'],
+ source: {
+ name: 'multifield',
+ type: 'object',
+ },
+ childFieldsName: 'properties',
+ canHaveChildFields: true,
+ hasChildFields: true,
+ canHaveMultiFields: false,
+ hasMultiFields: false,
+ isExpanded: false,
+ childFields: ['4459e8f2-3bec-4d17-b50c-f62d1fcbcca0', '9031c735-a445-491f-948d-41989b51a1a3'],
+ },
+ '54204b52-c6a0-4de4-8f82-3e1ea9ad533a': {
+ id: '54204b52-c6a0-4de4-8f82-3e1ea9ad533a',
+ nestedDepth: 0,
+ isMultiField: false,
+ path: ['title'],
+ source: {
+ name: 'title',
+ type: 'text',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ },
+ aliases: {},
+ rootLevelFields: ['dd0dd3aa-52c9-472b-a23d-ecec0a1b2420', '54204b52-c6a0-4de4-8f82-3e1ea9ad533a'],
+ maxNestedDepth: 3,
+};
describe('utils', () => {
describe('stripUndefinedValues()', () => {
@@ -101,4 +237,210 @@ describe('utils', () => {
).toEqual(false);
});
});
+ describe('getFieldsFromState', () => {
+ test('returns all the fields', () => {
+ expect(getFieldsFromState(fieldsWithnestedFields)).toEqual([
+ {
+ id: 'dd0dd3aa-52c9-472b-a23d-ecec0a1b2420',
+ nestedDepth: 0,
+ isMultiField: false,
+ path: ['multifield'],
+ source: {
+ name: 'multifield',
+ type: 'object',
+ },
+ childFieldsName: 'properties',
+ canHaveChildFields: true,
+ hasChildFields: true,
+ canHaveMultiFields: false,
+ hasMultiFields: false,
+ isExpanded: false,
+ childFields: [
+ '4459e8f2-3bec-4d17-b50c-f62d1fcbcca0',
+ '9031c735-a445-491f-948d-41989b51a1a3',
+ ],
+ },
+ {
+ id: '54204b52-c6a0-4de4-8f82-3e1ea9ad533a',
+ nestedDepth: 0,
+ isMultiField: false,
+ path: ['title'],
+ source: {
+ name: 'title',
+ type: 'text',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ ]);
+ });
+ test('returns only text fields matching filter', () => {
+ expect(getFieldsFromState(fieldsWithnestedFields, ['Text'])).toEqual([
+ {
+ id: 'af9b7a29-8c44-4dbe-baa0-c29eb1760d96',
+ parentId: '9031c735-a445-491f-948d-41989b51a1a3',
+ nestedDepth: 2,
+ isMultiField: false,
+ path: ['multifield', 'points', 'name'],
+ source: {
+ name: 'name',
+ type: 'text',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ {
+ id: '54204b52-c6a0-4de4-8f82-3e1ea9ad533a',
+ nestedDepth: 0,
+ isMultiField: false,
+ path: ['title'],
+ source: {
+ name: 'title',
+ type: 'text',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ ]);
+ });
+ });
+ describe('getallFieldsIncludingNestedFields', () => {
+ const fields: Fields = {
+ nested_field: {
+ properties: {
+ flag: { type: 'boolean' },
+ points: {
+ properties: {
+ name: { type: 'text' },
+ entity: {
+ type: 'object',
+ properties: {
+ entity_1: { type: 'keyword' },
+ },
+ },
+ },
+ type: 'object',
+ },
+ },
+ type: 'object',
+ },
+ };
+ test('returns all the data types including nested fields types', () => {
+ expect(getAllFieldTypesFromState(fields)).toEqual(['object', 'boolean', 'text', 'keyword']);
+ });
+ });
+
+ describe('getFieldsMatchingFilterFromState', () => {
+ const sampleState: State = {
+ isValid: true,
+ configuration: {
+ defaultValue: {},
+ data: {
+ internal: {},
+ format: () => ({}),
+ },
+ validate: () => Promise.resolve(true),
+ },
+ templates: {
+ defaultValue: {},
+ data: {
+ internal: {},
+ format: () => ({}),
+ },
+ validate: () => Promise.resolve(true),
+ },
+ fields: fieldsWithnestedFields,
+ documentFields: {
+ status: 'disabled',
+ editor: 'default',
+ },
+ runtimeFields: {},
+ runtimeFieldsList: {
+ status: 'idle',
+ },
+ fieldsJsonEditor: {
+ format: () => ({}),
+ isValid: true,
+ },
+ search: {
+ term: 'f',
+ result: [],
+ },
+ filter: {
+ filteredFields: [
+ {
+ id: 'eb903187-c99e-4773-9274-cbefc68bb3f1',
+ parentId: '5c6287de-7ed0-48f8-bc08-c401bcc26e40',
+ nestedDepth: 1,
+ isMultiField: false,
+ path: ['multifield', 'flag'],
+ source: {
+ name: 'flag',
+ type: 'boolean',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ ],
+ selectedOptions: [
+ {
+ label: 'Object',
+ 'data-test-subj': 'indexDetailsMappingsSelectFilter-object',
+ },
+ {
+ checked: 'on',
+ label: 'Boolean',
+ 'data-test-subj': 'indexDetailsMappingsSelectFilter-boolean',
+ },
+ {
+ label: 'Keyword',
+ 'data-test-subj': 'indexDetailsMappingsSelectFilter-keyword',
+ },
+ {
+ label: 'Text',
+ 'data-test-subj': 'indexDetailsMappingsSelectFilter-text',
+ },
+ ],
+ selectedDataTypes: ['Boolean'],
+ },
+ inferenceToModelIdMap: {},
+ };
+ test('returns list of matching fields with search term', () => {
+ expect(getFieldsMatchingFilterFromState(sampleState, ['Boolean'])).toEqual({
+ '4459e8f2-3bec-4d17-b50c-f62d1fcbcca0': {
+ id: '4459e8f2-3bec-4d17-b50c-f62d1fcbcca0',
+ parentId: 'dd0dd3aa-52c9-472b-a23d-ecec0a1b2420',
+ nestedDepth: 1,
+ isMultiField: false,
+ path: ['multifield', 'flag'],
+ source: {
+ name: 'flag',
+ type: 'boolean',
+ },
+ childFieldsName: 'fields',
+ canHaveChildFields: false,
+ hasChildFields: false,
+ canHaveMultiFields: true,
+ hasMultiFields: false,
+ isExpanded: false,
+ },
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts
index f432cfdead2fa5..cb95da745a30d2 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts
@@ -19,6 +19,7 @@ import {
NormalizedField,
NormalizedFields,
NormalizedRuntimeFields,
+ State,
ParameterName,
RuntimeFields,
SubType,
@@ -602,3 +603,85 @@ export const deNormalizeRuntimeFields = (fields: NormalizedRuntimeFields): Runti
};
}, {} as RuntimeFields);
};
+
+/**
+ * get all the fields from given state which matches selected DataTypes from filter
+ *
+ * @param state The state that we are using depending on the context (when adding new fields, static state is used)
+ * @param filteredDataTypes data types array from which fields are filtered from given state
+ */
+
+export const getFieldsMatchingFilterFromState = (
+ state: State,
+ filteredDataTypes: string[]
+): {
+ [id: string]: NormalizedField;
+} => {
+ return Object.fromEntries(
+ Object.entries(state.fields.byId).filter(([_, fieldId]) =>
+ filteredDataTypes.includes(TYPE_DEFINITION[state.fields.byId[fieldId.id].source.type].label)
+ )
+ );
+};
+
+/** accepts Generics argument and returns value, if value is not null or undefined
+ * @param value
+ */
+function isNotNullish(value: T | null | undefined): value is T {
+ return value !== null && value !== undefined;
+}
+
+/** returns normalized field that matches the dataTypes from the filteredDataTypes array
+ * @param normalizedFields fields that we are using, depending on the context (when adding new fields, static state is used)
+ * @param filteredDataTypes data types array from which fields are filtered from given state. When there are no filter selected, array would be undefined
+ */
+export const getFieldsFromState = (
+ normalizedFields: NormalizedFields,
+ filteredDataTypes?: string[]
+): NormalizedField[] => {
+ const getField = (fieldId: string) => {
+ if (filteredDataTypes) {
+ if (
+ filteredDataTypes.includes(
+ TYPE_DEFINITION[normalizedFields.byId[fieldId].source.type].label
+ )
+ ) {
+ return normalizedFields.byId[fieldId];
+ }
+ } else {
+ return normalizedFields.byId[fieldId];
+ }
+ };
+ const fields: Array = filteredDataTypes
+ ? Object.entries(normalizedFields.byId).map(([key, _]) => getField(key))
+ : normalizedFields.rootLevelFields.map((id) => getField(id));
+ return fields.filter(isNotNullish);
+};
+/**
+ * returns true if given value is first occurence of array
+ * useful when filtering unique values of an array
+ */
+function filterUnique(value: T, index: number, array: T[]) {
+ return array.indexOf(value) === index;
+}
+/**
+ * returns array consisting of all field types from state's fields including nested fields
+ * @param fields
+ */
+const getallFieldsIncludingNestedFields = (fields: Fields, fieldsArray: DataType[]) => {
+ const fieldsValue = Object.values(fields);
+ for (const field of fieldsValue) {
+ if (field.type) fieldsArray.push(field.type);
+ if (field.fields) getallFieldsIncludingNestedFields(field.fields, fieldsArray);
+ if (field.properties) getallFieldsIncludingNestedFields(field.properties, fieldsArray);
+ }
+ return fieldsArray;
+};
+
+/** returns all field types from the fields, including multifield and child fields
+ * @param allFields fields from state
+ */
+export const getAllFieldTypesFromState = (allFields: Fields): DataType[] => {
+ const fields: DataType[] = [];
+ return getallFieldsIncludingNestedFields(allFields, fields).filter(filterUnique);
+};
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx
index ac41436f26c9dc..2b132d377918d5 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx
@@ -54,6 +54,11 @@ export const StateProvider: React.FC<{ children?: React.ReactNode }> = ({ childr
term: '',
result: [],
},
+ filter: {
+ filteredFields: [],
+ selectedOptions: [],
+ selectedDataTypes: [],
+ },
inferenceToModelIdMap: {},
};
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
index b2b8deeff5555a..0e972e9900b9cb 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
@@ -9,6 +9,8 @@ import { PARAMETERS_DEFINITION } from './constants';
import {
getAllChildFields,
getFieldMeta,
+ getFieldsFromState,
+ getFieldsMatchingFilterFromState,
getMaxNestedDepth,
getUniqueId,
normalize,
@@ -205,6 +207,11 @@ export const reducer = (state: State, action: Action): State => {
term: '',
result: [],
},
+ filter: {
+ filteredFields: action.value.filter.filteredFields,
+ selectedOptions: action.value.filter.selectedOptions,
+ selectedDataTypes: action.value.filter.selectedDataTypes,
+ },
};
}
case 'configuration.update': {
@@ -604,7 +611,12 @@ export const reducer = (state: State, action: Action): State => {
...state,
search: {
term: action.value,
- result: searchFields(action.value, state.fields.byId),
+ result: searchFields(
+ action.value,
+ state.filter.selectedDataTypes.length > 0
+ ? getFieldsMatchingFilterFromState(state, state.filter.selectedDataTypes)
+ : state.fields.byId
+ ),
},
};
}
@@ -614,6 +626,22 @@ export const reducer = (state: State, action: Action): State => {
isValid: action.value,
};
}
+ case 'filter:update': {
+ const selectedDataTypes: string[] = action.value.selectedOptions
+ .filter((option) => option.checked === 'on')
+ .map((option) => option.label);
+ return {
+ ...state,
+ filter: {
+ filteredFields: getFieldsFromState(
+ state.fields,
+ selectedDataTypes.length > 0 ? selectedDataTypes : undefined
+ ),
+ selectedOptions: action.value.selectedOptions,
+ selectedDataTypes,
+ },
+ };
+ }
case 'inferenceToModelIdMap.update': {
return {
...state,
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts
index 555709008b8dd5..d08c80835c6201 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts
@@ -5,10 +5,12 @@
* 2.0.
*/
+import { EuiSelectableOption } from '@elastic/eui';
import { InferenceToModelIdMap } from '../components/document_fields/fields';
import { FormHook, OnFormUpdateArg, RuntimeField } from '../shared_imports';
import {
Field,
+ NormalizedField,
NormalizedFields,
NormalizedRuntimeField,
NormalizedRuntimeFields,
@@ -94,6 +96,11 @@ export interface State {
format(): MappingsFields;
isValid: boolean;
};
+ filter: {
+ filteredFields: NormalizedField[];
+ selectedOptions: EuiSelectableOption[];
+ selectedDataTypes: string[];
+ };
search: {
term: string;
result: SearchResult[];
@@ -130,6 +137,7 @@ export type Action =
| { type: 'runtimeField.edit'; value: NormalizedRuntimeField }
| { type: 'fieldsJsonEditor.update'; value: { json: { [key: string]: any }; isValid: boolean } }
| { type: 'search:update'; value: string }
- | { type: 'validity:update'; value: boolean };
+ | { type: 'validity:update'; value: boolean }
+ | { type: 'filter:update'; value: { selectedOptions: EuiSelectableOption[] } };
export type Dispatch = (action: Action) => void;
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
index 889ccf1f7ffafd..1bd58bcc8e5f09 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
@@ -7,6 +7,7 @@
import { useEffect, useMemo } from 'react';
+import { EuiSelectableOption } from '@elastic/eui';
import {
DocumentFieldsStatus,
Field,
@@ -22,8 +23,11 @@ import {
stripUndefinedValues,
normalizeRuntimeFields,
deNormalizeRuntimeFields,
+ getAllFieldTypesFromState,
+ getFieldsFromState,
} from './lib';
import { useMappingsState, useDispatch } from './mappings_state_context';
+import { TYPE_DEFINITION } from './constants';
interface Args {
onChange?: OnUpdateHandler;
@@ -47,6 +51,14 @@ export const useMappingsStateListener = ({ onChange, value, status }: Args) => {
() => normalizeRuntimeFields(runtimeFields),
[runtimeFields]
);
+ const fieldTypesOptions: EuiSelectableOption[] = useMemo(() => {
+ const allFieldsTypes = getAllFieldTypesFromState(deNormalize(normalize(mappedFields)));
+ return allFieldsTypes.map((dataType) => ({
+ checked: undefined,
+ label: TYPE_DEFINITION[dataType].label,
+ 'data-test-subj': `indexDetailsMappingsSelectFilter-${dataType}`,
+ }));
+ }, [mappedFields]);
const calculateStatus = (fieldStatus: string | undefined, rootLevelFields: string | any[]) => {
if (fieldStatus) return fieldStatus;
@@ -163,7 +175,19 @@ export const useMappingsStateListener = ({ onChange, value, status }: Args) => {
editor: 'default',
},
runtimeFields: parsedRuntimeFieldsDefaultValue,
+ filter: {
+ selectedOptions: fieldTypesOptions,
+ filteredFields: getFieldsFromState(parsedFieldsDefaultValue),
+ selectedDataTypes: [],
+ },
},
});
- }, [value, parsedFieldsDefaultValue, dispatch, status, parsedRuntimeFieldsDefaultValue]);
+ }, [
+ value,
+ parsedFieldsDefaultValue,
+ dispatch,
+ status,
+ parsedRuntimeFieldsDefaultValue,
+ fieldTypesOptions,
+ ]);
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_filter_fields.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_filter_fields.tsx
new file mode 100644
index 00000000000000..41c4e4e95c1866
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_filter_fields.tsx
@@ -0,0 +1,160 @@
+/*
+ * 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 {
+ EuiFilterButton,
+ EuiFilterGroup,
+ EuiPopover,
+ EuiPopoverTitle,
+ EuiSelectable,
+ EuiSelectableOption,
+} from '@elastic/eui';
+import React, { useCallback, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { useDispatch } from '../../../../components/mappings_editor/mappings_state_context';
+import { State } from '../../../../components/mappings_editor/types';
+import {
+ getFieldsFromState,
+ getFieldsMatchingFilterFromState,
+ searchFields,
+} from '../../../../components/mappings_editor/lib';
+
+interface Props {
+ isAddingFields: boolean;
+ isJSONVisible: boolean;
+ previousState: State;
+ setPreviousState: (state: State) => void;
+ state: State;
+}
+export const MappingsFilter: React.FC = ({
+ isAddingFields,
+ isJSONVisible,
+ previousState,
+ setPreviousState,
+ state,
+}) => {
+ const [isFilterByPopoverVisible, setIsFilterPopoverVisible] = useState(false);
+ const dispatch = useDispatch();
+ const setSelectedOptions = useCallback(
+ (options) => {
+ dispatch({
+ type: 'filter:update',
+ value: {
+ selectedOptions: options,
+ },
+ });
+ dispatch({
+ type: 'search:update',
+ value: state.search.term,
+ });
+ },
+ [dispatch, state.search.term]
+ );
+ const setPreviousStateSelectedOptions = useCallback(
+ (options: EuiSelectableOption[]) => {
+ const selectedDataTypes: string[] = options
+ .filter((option) => option.checked === 'on')
+ .map((option) => option.label);
+
+ setPreviousState({
+ ...previousState,
+ filter: {
+ filteredFields: getFieldsFromState(
+ previousState.fields,
+ selectedDataTypes.length > 0 ? selectedDataTypes : undefined
+ ),
+ selectedOptions: options,
+ selectedDataTypes,
+ },
+ search: {
+ term: previousState.search.term,
+ result: searchFields(
+ previousState.search.term,
+ selectedDataTypes.length > 0
+ ? getFieldsMatchingFilterFromState(previousState, selectedDataTypes)
+ : previousState.fields.byId
+ ),
+ },
+ });
+ },
+ [previousState, setPreviousState]
+ );
+ const filterByFieldTypeButton = (
+ setIsFilterPopoverVisible(!isFilterByPopoverVisible)}
+ numFilters={
+ !isAddingFields
+ ? state.filter.selectedOptions.length
+ : previousState.filter.selectedOptions.length
+ }
+ hasActiveFilters={
+ !isAddingFields
+ ? state.filter.selectedDataTypes.length > 0
+ : previousState.filter.selectedDataTypes.length > 0
+ }
+ numActiveFilters={
+ !isAddingFields
+ ? state.filter.selectedDataTypes.length
+ : previousState.filter.selectedDataTypes.length
+ }
+ isSelected={isFilterByPopoverVisible}
+ data-test-subj="indexDetailsMappingsFilterByFieldTypeButton"
+ >
+ {i18n.translate('xpack.idxMgmt.indexDetails.mappings.filterByFieldType.button', {
+ defaultMessage: 'Field types',
+ })}
+
+ );
+ return (
+
+ setIsFilterPopoverVisible(!isFilterByPopoverVisible)}
+ anchorPosition="downCenter"
+ data-test-subj="indexDetailsMappingsFilter"
+ >
+ {
+ if (!isAddingFields) {
+ setSelectedOptions(options);
+ } else {
+ setPreviousStateSelectedOptions(options);
+ }
+ }}
+ >
+ {(list, search) => (
+
+
+ {search}
+
+ {list}
+
+ )}
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx
index a5f5cccd025d42..71181796f6cbfc 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx
@@ -42,10 +42,12 @@ import {
useMappingsState,
} from '../../../../components/mappings_editor/mappings_state_context';
import {
- NormalizedField,
- NormalizedFields,
- State,
-} from '../../../../components/mappings_editor/types';
+ getFieldsFromState,
+ getFieldsMatchingFilterFromState,
+} from '../../../../components/mappings_editor/lib';
+import { NormalizedFields, State } from '../../../../components/mappings_editor/types';
+import { MappingsFilter } from './details_page_filter_fields';
+
import { useMappingsStateListener } from '../../../../components/mappings_editor/use_state_listener';
import { documentationService } from '../../../../services';
import { updateIndexMappings } from '../../../../services/api';
@@ -54,16 +56,6 @@ import { SemanticTextBanner } from './semantic_text_banner';
import { TrainedModelsDeploymentModal } from './trained_models_deployment_modal';
import { parseMappings } from '../../../../shared/parse_mappings';
-const getFieldsFromState = (state: State) => {
- const getField = (fieldId: string) => {
- return state.fields.byId[fieldId];
- };
- const fields = () => {
- return state.fields.rootLevelFields.map((id) => getField(id));
- };
- return fields();
-};
-
export const DetailsPageMappingsContent: FunctionComponent<{
index: Index;
data: string;
@@ -111,9 +103,13 @@ export const DetailsPageMappingsContent: FunctionComponent<{
}, [state.fields.byId]);
const [previousState, setPreviousState] = useState(state);
- const [previousStateFields, setPreviousStateFields] = useState(
- getFieldsFromState(state)
- );
+
+ const previousStateSelectedDataTypes: string[] = useMemo(() => {
+ return previousState.filter.selectedOptions
+ .filter((option) => option.checked === 'on')
+ .map((option) => option.label);
+ }, [previousState.filter.selectedOptions]);
+
const [saveMappingError, setSaveMappingError] = useState(undefined);
const [isJSONVisible, setIsJSONVisible] = useState(false);
const onToggleChange = () => {
@@ -151,7 +147,6 @@ export const DetailsPageMappingsContent: FunctionComponent<{
setAddingFields(!isAddingFields);
// when adding new field, save previous state. This state is then used by FieldsList component to show only saved mappings.
- setPreviousStateFields(getFieldsFromState(state));
setPreviousState(state);
// reset mappings and change status to create field.
@@ -160,6 +155,11 @@ export const DetailsPageMappingsContent: FunctionComponent<{
value: {
...state,
fields: { ...state.fields, byId: {}, rootLevelFields: [] } as NormalizedFields,
+ filter: {
+ filteredFields: [],
+ selectedOptions: [],
+ selectedDataTypes: [],
+ },
documentFields: {
status: 'creatingField',
editor: 'default',
@@ -235,16 +235,39 @@ export const DetailsPageMappingsContent: FunctionComponent<{
...previousState,
search: {
term: value,
- result: searchFields(value, previousState.fields.byId),
+ result: searchFields(
+ value,
+ previousStateSelectedDataTypes.length > 0
+ ? getFieldsMatchingFilterFromState(previousState, previousStateSelectedDataTypes)
+ : previousState.fields.byId
+ ),
},
});
} else {
dispatch({ type: 'search:update', value });
}
},
- [dispatch, previousState, isAddingFields]
+ [dispatch, previousState, isAddingFields, previousStateSelectedDataTypes]
);
+ const onClearSearch = useCallback(() => {
+ setPreviousState({
+ ...previousState,
+ search: {
+ term: '',
+ result: searchFields(
+ '',
+ previousState.filter.selectedDataTypes.length > 0
+ ? getFieldsMatchingFilterFromState(
+ previousState,
+ previousState.filter.selectedDataTypes
+ )
+ : previousState.fields.byId
+ ),
+ },
+ });
+ }, [previousState]);
+
const searchTerm = isAddingFields ? previousState.search.term.trim() : state.search.term.trim();
const jsonBlock = (
@@ -263,6 +286,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{
) : (
@@ -270,13 +294,25 @@ export const DetailsPageMappingsContent: FunctionComponent<{
const fieldsListComponent = isAddingFields ? (
0
+ ? previousState.filter.filteredFields
+ : getFieldsFromState(previousState.fields)
+ }
state={previousState}
setPreviousState={setPreviousState}
isAddingFields={isAddingFields}
/>
) : (
-
+ 0
+ ? state.filter.filteredFields
+ : getFieldsFromState(state.fields)
+ }
+ state={state}
+ isAddingFields={isAddingFields}
+ />
);
const fieldSearchComponent = isAddingFields ? (
+
+
+
{fieldSearchComponent}
{!index.hidden && (
diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js
index 9e8dfe0b1eb985..27d158dfb130f4 100644
--- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js
+++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js
@@ -254,7 +254,7 @@ class TimeseriesChartIntl extends Component {
}
this.rowMouseenterSubscriber = mlTableService.rowMouseenter$.subscribe(
- tableRecordMousenterListener
+ tableRecordMousenterListener.bind(this)
);
this.rowMouseleaveSubscriber = mlTableService.rowMouseleave$.subscribe(
tableRecordMouseleaveListener
diff --git a/x-pack/plugins/observability_solution/apm/server/plugin.ts b/x-pack/plugins/observability_solution/apm/server/plugin.ts
index 2c2392b8454158..7e93a5f3c33246 100644
--- a/x-pack/plugins/observability_solution/apm/server/plugin.ts
+++ b/x-pack/plugins/observability_solution/apm/server/plugin.ts
@@ -40,7 +40,7 @@ import { createApmSourceMapIndexTemplate } from './routes/source_maps/create_apm
import { addApiKeysToEveryPackagePolicyIfMissing } from './routes/fleet/api_keys/add_api_keys_to_policies_if_missing';
import { apmTutorialCustomIntegration } from '../common/tutorial/tutorials';
import { registerAssistantFunctions } from './assistant_functions';
-import { getAlertDetailsContextHandler } from './routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler';
+import { getAlertDetailsContextHandler } from './routes/assistant_functions/get_observability_alert_details_context';
export class APMPlugin
implements Plugin
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts
index 2ffbdc30a1c525..99ea7ec691dabe 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import moment from 'moment';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client';
import { ApmTimeseriesType, getApmTimeseries, TimeseriesChangePoint } from '../get_apm_timeseries';
@@ -18,14 +17,16 @@ export interface ChangePointGrouping {
export async function getServiceChangePoints({
apmEventClient,
- alertStartedAt,
+ start,
+ end,
serviceName,
serviceEnvironment,
transactionType,
transactionName,
}: {
apmEventClient: APMEventClient;
- alertStartedAt: string;
+ start: string;
+ end: string;
serviceName: string | undefined;
serviceEnvironment: string | undefined;
transactionType: string | undefined;
@@ -38,8 +39,8 @@ export async function getServiceChangePoints({
const res = await getApmTimeseries({
apmEventClient,
arguments: {
- start: moment(alertStartedAt).subtract(12, 'hours').toISOString(),
- end: alertStartedAt,
+ start,
+ end,
stats: [
{
title: 'Latency',
@@ -95,12 +96,14 @@ export async function getServiceChangePoints({
export async function getExitSpanChangePoints({
apmEventClient,
- alertStartedAt,
+ start,
+ end,
serviceName,
serviceEnvironment,
}: {
apmEventClient: APMEventClient;
- alertStartedAt: string;
+ start: string;
+ end: string;
serviceName: string | undefined;
serviceEnvironment: string | undefined;
}): Promise {
@@ -111,8 +114,8 @@ export async function getExitSpanChangePoints({
const res = await getApmTimeseries({
apmEventClient,
arguments: {
- start: moment(alertStartedAt).subtract(30, 'minute').toISOString(),
- end: alertStartedAt,
+ start,
+ end,
stats: [
{
title: 'Exit span latency',
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts
index 990b63f412f764..e3e570c05e13ed 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts
@@ -17,13 +17,11 @@ import {
} from '../../../../common/es_fields/apm';
import { getTypedSearch } from '../../../utils/create_typed_es_client';
-export type LogCategories =
- | Array<{
- errorCategory: string;
- docCount: number;
- sampleMessage: string;
- }>
- | undefined;
+export interface LogCategory {
+ errorCategory: string;
+ docCount: number;
+ sampleMessage: string;
+}
export async function getLogCategories({
esClient,
@@ -40,7 +38,7 @@ export async function getLogCategories({
'container.id'?: string;
'kubernetes.pod.name'?: string;
};
-}): Promise {
+}): Promise {
const start = datemath.parse(args.start)?.valueOf()!;
const end = datemath.parse(args.end)?.valueOf()!;
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler.ts
deleted file mode 100644
index cd1a56d56f45e4..00000000000000
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler.ts
+++ /dev/null
@@ -1,85 +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 { Logger } from '@kbn/core/server';
-import {
- AlertDetailsContextualInsightsHandlerQuery,
- AlertDetailsContextualInsightsRequestContext,
-} from '@kbn/observability-plugin/server/services';
-import { getApmAlertsClient } from '../../../lib/helpers/get_apm_alerts_client';
-import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client';
-import { getMlClient } from '../../../lib/helpers/get_ml_client';
-import { getRandomSampler } from '../../../lib/helpers/get_random_sampler';
-import { getObservabilityAlertDetailsContext } from '.';
-import { APMRouteHandlerResources } from '../../apm_routes/register_apm_server_routes';
-
-export const getAlertDetailsContextHandler = (
- resourcePlugins: APMRouteHandlerResources['plugins'],
- logger: Logger
-) => {
- return async (
- requestContext: AlertDetailsContextualInsightsRequestContext,
- query: AlertDetailsContextualInsightsHandlerQuery
- ) => {
- const resources = {
- getApmIndices: async () => {
- const coreContext = await requestContext.core;
- return resourcePlugins.apmDataAccess.setup.getApmIndices(coreContext.savedObjects.client);
- },
- request: requestContext.request,
- params: { query: { _inspect: false } },
- plugins: resourcePlugins,
- context: {
- core: requestContext.core,
- licensing: requestContext.licensing,
- alerting: resourcePlugins.alerting!.start().then((startContract) => {
- return {
- getRulesClient() {
- return startContract.getRulesClientWithRequest(requestContext.request);
- },
- };
- }),
- rac: resourcePlugins.ruleRegistry.start().then((startContract) => {
- return {
- getAlertsClient() {
- return startContract.getRacClientWithRequest(requestContext.request);
- },
- };
- }),
- },
- };
-
- const [apmEventClient, annotationsClient, apmAlertsClient, coreContext, mlClient] =
- await Promise.all([
- getApmEventClient(resources),
- resourcePlugins.observability.setup.getScopedAnnotationsClient(
- resources.context,
- requestContext.request
- ),
- getApmAlertsClient(resources),
- requestContext.core,
- getMlClient(resources),
- getRandomSampler({
- security: resourcePlugins.security,
- probability: 1,
- request: requestContext.request,
- }),
- ]);
- const esClient = coreContext.elasticsearch.client.asCurrentUser;
-
- return getObservabilityAlertDetailsContext({
- coreContext,
- apmEventClient,
- annotationsClient,
- apmAlertsClient,
- mlClient,
- esClient,
- query,
- logger,
- });
- };
-};
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_apm_alert_details_context_prompt.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_apm_alert_details_context_prompt.ts
deleted file mode 100644
index 4a28a0460ebbdd..00000000000000
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_apm_alert_details_context_prompt.ts
+++ /dev/null
@@ -1,85 +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 { isEmpty } from 'lodash';
-import { AlertDetailsContextualInsight } from '@kbn/observability-plugin/server/services';
-import { APMDownstreamDependency } from '../get_apm_downstream_dependencies';
-import { ServiceSummary } from '../get_apm_service_summary';
-import { LogCategories } from '../get_log_categories';
-import { ApmAnomalies } from '../get_apm_service_summary/get_anomalies';
-import { ChangePointGrouping } from '../get_changepoints';
-
-export function getApmAlertDetailsContextPrompt({
- serviceName,
- serviceEnvironment,
- serviceSummary,
- downstreamDependencies,
- logCategories,
- serviceChangePoints,
- exitSpanChangePoints,
- anomalies,
-}: {
- serviceName?: string;
- serviceEnvironment?: string;
- serviceSummary?: ServiceSummary;
- downstreamDependencies?: APMDownstreamDependency[];
- logCategories: LogCategories;
- serviceChangePoints?: ChangePointGrouping[];
- exitSpanChangePoints?: ChangePointGrouping[];
- anomalies?: ApmAnomalies;
-}): AlertDetailsContextualInsight[] {
- const prompt: AlertDetailsContextualInsight[] = [];
- if (!isEmpty(serviceSummary)) {
- prompt.push({
- key: 'serviceSummary',
- description: 'Metadata for the service where the alert occurred',
- data: serviceSummary,
- });
- }
-
- if (!isEmpty(downstreamDependencies)) {
- prompt.push({
- key: 'downstreamDependencies',
- description: `Downstream dependencies from the service "${serviceName}". Problems in these services can negatively affect the performance of "${serviceName}"`,
- data: downstreamDependencies,
- });
- }
-
- if (!isEmpty(serviceChangePoints)) {
- prompt.push({
- key: 'serviceChangePoints',
- description: `Significant change points for "${serviceName}". Use this to spot dips and spikes in throughput, latency and failure rate`,
- data: serviceChangePoints,
- });
- }
-
- if (!isEmpty(exitSpanChangePoints)) {
- prompt.push({
- key: 'exitSpanChangePoints',
- description: `Significant change points for the dependencies of "${serviceName}". Use this to spot dips or spikes in throughput, latency and failure rate for downstream dependencies`,
- data: exitSpanChangePoints,
- });
- }
-
- if (!isEmpty(logCategories)) {
- prompt.push({
- key: 'logCategories',
- description: `Log events occurring around the time of the alert`,
- data: logCategories,
- });
- }
-
- if (!isEmpty(anomalies)) {
- prompt.push({
- key: 'anomalies',
- description: `Anomalies for services running in the environment "${serviceEnvironment}"`,
- data: anomalies,
- });
- }
-
- return prompt;
-}
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts
index 22679dd55ded02..638903e8135452 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts
@@ -12,7 +12,7 @@ import { rangeQuery, typedSearch } from '@kbn/observability-plugin/server/utils/
import * as t from 'io-ts';
import moment from 'moment';
import { ESSearchRequest } from '@kbn/es-types';
-import { observabilityAlertDetailsContextRt } from '@kbn/observability-plugin/server/services';
+import { alertDetailsContextRt } from '@kbn/observability-plugin/server/services';
import { ApmDocumentType } from '../../../../common/document_type';
import {
APMEventClient,
@@ -26,7 +26,7 @@ export async function getContainerIdFromSignals({
coreContext,
apmEventClient,
}: {
- query: t.TypeOf;
+ query: t.TypeOf;
esClient: ElasticsearchClient;
coreContext: Pick;
apmEventClient: APMEventClient;
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts
index bd62b998bee994..284c286766c769 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts
@@ -12,7 +12,7 @@ import { rangeQuery, termQuery, typedSearch } from '@kbn/observability-plugin/se
import * as t from 'io-ts';
import moment from 'moment';
import { ESSearchRequest } from '@kbn/es-types';
-import { observabilityAlertDetailsContextRt } from '@kbn/observability-plugin/server/services';
+import { alertDetailsContextRt } from '@kbn/observability-plugin/server/services';
import { ApmDocumentType } from '../../../../common/document_type';
import {
APMEventClient,
@@ -26,7 +26,7 @@ export async function getServiceNameFromSignals({
coreContext,
apmEventClient,
}: {
- query: t.TypeOf;
+ query: t.TypeOf;
esClient: ElasticsearchClient;
coreContext: Pick;
apmEventClient: APMEventClient;
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts
index d6022876c9f3b9..85032b2f05b531 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts
@@ -5,176 +5,236 @@
* 2.0.
*/
-import type { ScopedAnnotationsClient } from '@kbn/observability-plugin/server';
-import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
-import type { CoreRequestHandlerContext, Logger } from '@kbn/core/server';
+import { Logger } from '@kbn/core/server';
import {
- AlertDetailsContextualInsight,
AlertDetailsContextualInsightsHandlerQuery,
+ AlertDetailsContextualInsightsRequestContext,
} from '@kbn/observability-plugin/server/services';
import moment from 'moment';
-import type { MlClient } from '../../../lib/helpers/get_ml_client';
-import type { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client';
-import type { ApmAlertsClient } from '../../../lib/helpers/get_apm_alerts_client';
+import { isEmpty } from 'lodash';
+import { getApmAlertsClient } from '../../../lib/helpers/get_apm_alerts_client';
+import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client';
+import { getMlClient } from '../../../lib/helpers/get_ml_client';
+import { getRandomSampler } from '../../../lib/helpers/get_random_sampler';
import { getApmServiceSummary } from '../get_apm_service_summary';
import { getAssistantDownstreamDependencies } from '../get_apm_downstream_dependencies';
import { getLogCategories } from '../get_log_categories';
import { getAnomalies } from '../get_apm_service_summary/get_anomalies';
import { getServiceNameFromSignals } from './get_service_name_from_signals';
import { getContainerIdFromSignals } from './get_container_id_from_signals';
-import { getApmAlertDetailsContextPrompt } from './get_apm_alert_details_context_prompt';
import { getExitSpanChangePoints, getServiceChangePoints } from '../get_changepoints';
+import { APMRouteHandlerResources } from '../../apm_routes/register_apm_server_routes';
-export async function getObservabilityAlertDetailsContext({
- coreContext,
- annotationsClient,
- apmAlertsClient,
- apmEventClient,
- esClient,
- logger,
- mlClient,
- query,
-}: {
- coreContext: Pick;
- annotationsClient?: ScopedAnnotationsClient;
- apmAlertsClient: ApmAlertsClient;
- apmEventClient: APMEventClient;
- esClient: ElasticsearchClient;
- logger: Logger;
- mlClient?: MlClient;
- query: AlertDetailsContextualInsightsHandlerQuery;
-}): Promise {
- const alertStartedAt = query.alert_started_at;
- const serviceEnvironment = query['service.environment'];
- const hostName = query['host.name'];
- const kubernetesPodName = query['kubernetes.pod.name'];
- const [serviceName, containerId] = await Promise.all([
- getServiceNameFromSignals({
- query,
- esClient,
- coreContext,
- apmEventClient,
- }),
- getContainerIdFromSignals({
- query,
- esClient,
- coreContext,
- apmEventClient,
- }),
- ]);
+export const getAlertDetailsContextHandler = (
+ resourcePlugins: APMRouteHandlerResources['plugins'],
+ logger: Logger
+) => {
+ return async (
+ requestContext: AlertDetailsContextualInsightsRequestContext,
+ query: AlertDetailsContextualInsightsHandlerQuery
+ ) => {
+ const resources = {
+ getApmIndices: async () => {
+ const coreContext = await requestContext.core;
+ return resourcePlugins.apmDataAccess.setup.getApmIndices(coreContext.savedObjects.client);
+ },
+ request: requestContext.request,
+ params: { query: { _inspect: false } },
+ plugins: resourcePlugins,
+ context: {
+ core: requestContext.core,
+ licensing: requestContext.licensing,
+ alerting: resourcePlugins.alerting!.start().then((startContract) => {
+ return {
+ getRulesClient() {
+ return startContract.getRulesClientWithRequest(requestContext.request);
+ },
+ };
+ }),
+ rac: resourcePlugins.ruleRegistry.start().then((startContract) => {
+ return {
+ getAlertsClient() {
+ return startContract.getRacClientWithRequest(requestContext.request);
+ },
+ };
+ }),
+ },
+ };
+
+ const [apmEventClient, annotationsClient, apmAlertsClient, coreContext, mlClient] =
+ await Promise.all([
+ getApmEventClient(resources),
+ resourcePlugins.observability.setup.getScopedAnnotationsClient(
+ resources.context,
+ requestContext.request
+ ),
+ getApmAlertsClient(resources),
+ requestContext.core,
+ getMlClient(resources),
+ getRandomSampler({
+ security: resourcePlugins.security,
+ probability: 1,
+ request: requestContext.request,
+ }),
+ ]);
+ const esClient = coreContext.elasticsearch.client.asCurrentUser;
+
+ const alertStartedAt = query.alert_started_at;
+ const serviceEnvironment = query['service.environment'];
+ const hostName = query['host.name'];
+ const kubernetesPodName = query['kubernetes.pod.name'];
+ const [serviceName, containerId] = await Promise.all([
+ getServiceNameFromSignals({
+ query,
+ esClient,
+ coreContext,
+ apmEventClient,
+ }),
+ getContainerIdFromSignals({
+ query,
+ esClient,
+ coreContext,
+ apmEventClient,
+ }),
+ ]);
- async function handleError(cb: () => Promise): Promise {
- try {
- return await cb();
- } catch (error) {
- logger.error('Error while fetching observability alert details context');
- logger.error(error);
- return;
+ async function handleError(cb: () => Promise): Promise {
+ try {
+ return await cb();
+ } catch (error) {
+ logger.error('Error while fetching observability alert details context');
+ logger.error(error);
+ return;
+ }
}
- }
- const serviceSummaryPromise = serviceName
- ? handleError(() =>
- getApmServiceSummary({
- apmEventClient,
- annotationsClient,
- esClient,
- apmAlertsClient,
- mlClient,
- logger,
- arguments: {
- 'service.name': serviceName,
- 'service.environment': serviceEnvironment,
- start: moment(alertStartedAt).subtract(5, 'minute').toISOString(),
- end: alertStartedAt,
- },
- })
- )
- : undefined;
+ const serviceSummaryPromise = serviceName
+ ? handleError(() =>
+ getApmServiceSummary({
+ apmEventClient,
+ annotationsClient,
+ esClient,
+ apmAlertsClient,
+ mlClient,
+ logger,
+ arguments: {
+ 'service.name': serviceName,
+ 'service.environment': serviceEnvironment,
+ start: moment(alertStartedAt).subtract(5, 'minute').toISOString(),
+ end: alertStartedAt,
+ },
+ })
+ )
+ : undefined;
- const downstreamDependenciesPromise = serviceName
- ? handleError(() =>
- getAssistantDownstreamDependencies({
- apmEventClient,
- arguments: {
- 'service.name': serviceName,
- 'service.environment': serviceEnvironment,
- start: moment(alertStartedAt).subtract(5, 'minute').toISOString(),
- end: alertStartedAt,
- },
- })
- )
- : undefined;
+ const downstreamDependenciesPromise = serviceName
+ ? handleError(() =>
+ getAssistantDownstreamDependencies({
+ apmEventClient,
+ arguments: {
+ 'service.name': serviceName,
+ 'service.environment': serviceEnvironment,
+ start: moment(alertStartedAt).subtract(15, 'minute').toISOString(),
+ end: alertStartedAt,
+ },
+ })
+ )
+ : undefined;
- const logCategoriesPromise = handleError(() =>
- getLogCategories({
- esClient,
- coreContext,
- arguments: {
- start: moment(alertStartedAt).subtract(5, 'minute').toISOString(),
- end: alertStartedAt,
- 'service.name': serviceName,
- 'host.name': hostName,
- 'container.id': containerId,
- 'kubernetes.pod.name': kubernetesPodName,
- },
- })
- );
+ const logCategoriesPromise = handleError(() =>
+ getLogCategories({
+ esClient,
+ coreContext,
+ arguments: {
+ start: moment(alertStartedAt).subtract(15, 'minute').toISOString(),
+ end: alertStartedAt,
+ 'service.name': serviceName,
+ 'host.name': hostName,
+ 'container.id': containerId,
+ 'kubernetes.pod.name': kubernetesPodName,
+ },
+ })
+ );
- const serviceChangePointsPromise = handleError(() =>
- getServiceChangePoints({
- apmEventClient,
- alertStartedAt,
- serviceName,
- serviceEnvironment,
- transactionType: query['transaction.type'],
- transactionName: query['transaction.name'],
- })
- );
+ const serviceChangePointsPromise = handleError(() =>
+ getServiceChangePoints({
+ apmEventClient,
+ start: moment(alertStartedAt).subtract(6, 'hours').toISOString(),
+ end: alertStartedAt,
+ serviceName,
+ serviceEnvironment,
+ transactionType: query['transaction.type'],
+ transactionName: query['transaction.name'],
+ })
+ );
- const exitSpanChangePointsPromise = handleError(() =>
- getExitSpanChangePoints({
- apmEventClient,
- alertStartedAt,
- serviceName,
- serviceEnvironment,
- })
- );
+ const exitSpanChangePointsPromise = handleError(() =>
+ getExitSpanChangePoints({
+ apmEventClient,
+ start: moment(alertStartedAt).subtract(6, 'hours').toISOString(),
+ end: alertStartedAt,
+ serviceName,
+ serviceEnvironment,
+ })
+ );
- const anomaliesPromise = handleError(() =>
- getAnomalies({
- start: moment(alertStartedAt).subtract(1, 'hour').valueOf(),
- end: moment(alertStartedAt).valueOf(),
- environment: serviceEnvironment,
- mlClient,
- logger,
- })
- );
+ const anomaliesPromise = handleError(() =>
+ getAnomalies({
+ start: moment(alertStartedAt).subtract(1, 'hour').valueOf(),
+ end: moment(alertStartedAt).valueOf(),
+ environment: serviceEnvironment,
+ mlClient,
+ logger,
+ })
+ );
- const [
- serviceSummary,
- downstreamDependencies,
- logCategories,
- serviceChangePoints,
- exitSpanChangePoints,
- anomalies,
- ] = await Promise.all([
- serviceSummaryPromise,
- downstreamDependenciesPromise,
- logCategoriesPromise,
- serviceChangePointsPromise,
- exitSpanChangePointsPromise,
- anomaliesPromise,
- ]);
+ const [
+ serviceSummary,
+ downstreamDependencies,
+ logCategories,
+ serviceChangePoints,
+ exitSpanChangePoints,
+ anomalies,
+ ] = await Promise.all([
+ serviceSummaryPromise,
+ downstreamDependenciesPromise,
+ logCategoriesPromise,
+ serviceChangePointsPromise,
+ exitSpanChangePointsPromise,
+ anomaliesPromise,
+ ]);
- return getApmAlertDetailsContextPrompt({
- serviceName,
- serviceEnvironment,
- serviceSummary,
- downstreamDependencies,
- logCategories,
- serviceChangePoints,
- exitSpanChangePoints,
- anomalies,
- });
-}
+ return [
+ {
+ key: 'serviceSummary',
+ description: 'Metadata for the service where the alert occurred',
+ data: serviceSummary,
+ },
+ {
+ key: 'downstreamDependencies',
+ description: `Downstream dependencies from the service "${serviceName}". Problems in these services can negatively affect the performance of "${serviceName}"`,
+ data: downstreamDependencies,
+ },
+ {
+ key: 'serviceChangePoints',
+ description: `Significant change points for "${serviceName}". Use this to spot dips and spikes in throughput, latency and failure rate`,
+ data: serviceChangePoints,
+ },
+ {
+ key: 'exitSpanChangePoints',
+ description: `Significant change points for the dependencies of "${serviceName}". Use this to spot dips or spikes in throughput, latency and failure rate for downstream dependencies`,
+ data: exitSpanChangePoints,
+ },
+ {
+ key: 'logCategories',
+ description: `Log events occurring around the time of the alert`,
+ data: logCategories,
+ },
+ {
+ key: 'anomalies',
+ description: `Anomalies for services running in the environment "${serviceEnvironment}"`,
+ data: anomalies,
+ },
+ ].filter(({ data }) => !isEmpty(data));
+ };
+};
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts
index af3dfac613bd57..68b7362f85d855 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts
@@ -6,16 +6,8 @@
*/
import * as t from 'io-ts';
import { omit } from 'lodash';
-import {
- AlertDetailsContextualInsight,
- observabilityAlertDetailsContextRt,
-} from '@kbn/observability-plugin/server/services';
-import { getApmAlertsClient } from '../../lib/helpers/get_apm_alerts_client';
import { getApmEventClient } from '../../lib/helpers/get_apm_event_client';
-import { getMlClient } from '../../lib/helpers/get_ml_client';
-import { getRandomSampler } from '../../lib/helpers/get_random_sampler';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
-import { getObservabilityAlertDetailsContext } from './get_observability_alert_details_context';
import {
downstreamDependenciesRouteRt,
@@ -24,49 +16,6 @@ import {
} from './get_apm_downstream_dependencies';
import { getApmTimeseries, getApmTimeseriesRt, type ApmTimeseries } from './get_apm_timeseries';
-const getObservabilityAlertDetailsContextRoute = createApmServerRoute({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- options: {
- tags: ['access:apm'],
- },
-
- params: t.type({
- query: observabilityAlertDetailsContextRt,
- }),
- handler: async (resources): Promise<{ context: AlertDetailsContextualInsight[] }> => {
- const { context, request, plugins, logger, params } = resources;
- const { query } = params;
-
- const [apmEventClient, annotationsClient, coreContext, apmAlertsClient, mlClient] =
- await Promise.all([
- getApmEventClient(resources),
- plugins.observability.setup.getScopedAnnotationsClient(context, request),
- context.core,
- getApmAlertsClient(resources),
- getMlClient(resources),
- getRandomSampler({
- security: resources.plugins.security,
- probability: 1,
- request: resources.request,
- }),
- ]);
- const esClient = coreContext.elasticsearch.client.asCurrentUser;
-
- const obsAlertContext = await getObservabilityAlertDetailsContext({
- coreContext,
- annotationsClient,
- apmAlertsClient,
- apmEventClient,
- esClient,
- logger,
- mlClient,
- query,
- });
-
- return { context: obsAlertContext };
- },
-});
-
const getApmTimeSeriesRoute = createApmServerRoute({
endpoint: 'POST /internal/apm/assistant/get_apm_timeseries',
options: {
@@ -120,6 +69,5 @@ const getDownstreamDependenciesRoute = createApmServerRoute({
export const assistantRouteRepository = {
...getApmTimeSeriesRoute,
- ...getObservabilityAlertDetailsContextRoute,
...getDownstreamDependenciesRoute,
};
diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
index 1de4b4a136919e..7754badbf121d8 100644
--- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
@@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import dedent from 'dedent';
+import { type AlertDetailsContextualInsight } from '../../../server/services';
import { useKibana } from '../../utils/kibana_react';
import { AlertData } from '../../hooks/use_fetch_alert_detail';
@@ -21,16 +22,16 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu
const ObservabilityAIAssistantContextualInsight =
observabilityAIAssistant?.ObservabilityAIAssistantContextualInsight;
- const getPromptMessages = useCallback(async () => {
+ const getAlertContextMessages = useCallback(async () => {
const fields = alert?.formatted.fields as Record | undefined;
if (!observabilityAIAssistant || !fields || !alert) {
return [];
}
try {
- const { context } = await http.get<{
- context: Array<{ description: string; data: unknown }>;
- }>('/internal/apm/assistant/alert_details_contextual_insights', {
+ const { alertContext } = await http.get<{
+ alertContext: AlertDetailsContextualInsight[];
+ }>('/internal/observability/assistant/alert_details_contextual_insights', {
query: {
alert_started_at: new Date(alert.formatted.start).toISOString(),
@@ -47,26 +48,28 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu
},
});
- const obsAlertContext = context
+ const obsAlertContext = alertContext
.map(({ description, data }) => `${description}:\n${JSON.stringify(data, null, 2)}`)
.join('\n\n');
return observabilityAIAssistant.getContextualInsightMessages({
message: `I'm looking at an alert and trying to understand why it was triggered`,
instructions: dedent(
- `I'm an SRE. I am looking at an alert that was triggered. I want to understand why it was triggered, what it means, and what I should do next.
+ `I'm an SRE. I am looking at an alert that was triggered. I want to understand why it was triggered, what it means, and what I should do next.
- The following contextual information is available to help me understand the alert:
+ The following contextual information is available to help you understand the alert:
${obsAlertContext}
Be brief and to the point.
Do not list the alert details as bullet points.
Refer to the contextual information provided above when relevant.
- Pay specific attention to why the alert happened and what may have contributed to it.
+ Pay special attention to regressions in downstream dependencies like big increases or decreases in throughput, latency or failure rate
+ Suggest reasons why the alert happened and what may have contributed to it.
`
),
});
} catch (e) {
+ console.error('An error occurred while fetching alert context', e);
return observabilityAIAssistant.getContextualInsightMessages({
message: `I'm looking at an alert and trying to understand why it was triggered`,
instructions: dedent(
@@ -88,7 +91,7 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu
'xpack.observability.alertDetailContextualInsights.InsightButtonLabel',
{ defaultMessage: 'Help me understand this alert' }
)}
- messages={getPromptMessages}
+ messages={getAlertContextMessages}
/>
diff --git a/x-pack/plugins/observability_solution/observability/server/routes/assistant/route.ts b/x-pack/plugins/observability_solution/observability/server/routes/assistant/route.ts
new file mode 100644
index 00000000000000..e6e04704971d22
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability/server/routes/assistant/route.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 * as t from 'io-ts';
+import { alertDetailsContextRt } from '../../services';
+import { createObservabilityServerRoute } from '../create_observability_server_route';
+
+const getObservabilityAlertDetailsContextRoute = createObservabilityServerRoute({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ options: {
+ tags: [],
+ },
+ params: t.type({
+ query: alertDetailsContextRt,
+ }),
+ handler: async ({ dependencies, params, context, request }) => {
+ const alertContext =
+ await dependencies.assistant.alertDetailsContextualInsightsService.getAlertDetailsContext(
+ {
+ core: context.core,
+ licensing: context.licensing,
+ request,
+ },
+ params.query
+ );
+
+ return { alertContext };
+ },
+});
+
+export const aiAssistantRouteRepository = {
+ ...getObservabilityAlertDetailsContextRoute,
+};
diff --git a/x-pack/plugins/observability_solution/observability/server/routes/get_global_observability_server_route_repository.ts b/x-pack/plugins/observability_solution/observability/server/routes/get_global_observability_server_route_repository.ts
index 78c4a2614b5289..1516c42f86fd10 100644
--- a/x-pack/plugins/observability_solution/observability/server/routes/get_global_observability_server_route_repository.ts
+++ b/x-pack/plugins/observability_solution/observability/server/routes/get_global_observability_server_route_repository.ts
@@ -5,11 +5,14 @@
* 2.0.
*/
+import { EndpointOf } from '@kbn/server-route-repository';
import { ObservabilityConfig } from '..';
+import { aiAssistantRouteRepository } from './assistant/route';
import { rulesRouteRepository } from './rules/route';
export function getObservabilityServerRouteRepository(config: ObservabilityConfig) {
const repository = {
+ ...aiAssistantRouteRepository,
...rulesRouteRepository,
};
return repository;
@@ -18,3 +21,5 @@ export function getObservabilityServerRouteRepository(config: ObservabilityConfi
export type ObservabilityServerRouteRepository = ReturnType<
typeof getObservabilityServerRouteRepository
>;
+
+export type APIEndpoint = EndpointOf;
diff --git a/x-pack/plugins/observability_solution/observability/server/routes/types.ts b/x-pack/plugins/observability_solution/observability/server/routes/types.ts
index a0ef6ee6c0c74b..39402531376406 100644
--- a/x-pack/plugins/observability_solution/observability/server/routes/types.ts
+++ b/x-pack/plugins/observability_solution/observability/server/routes/types.ts
@@ -7,12 +7,15 @@
import type { EndpointOf, ReturnOf, ServerRouteRepository } from '@kbn/server-route-repository';
import { KibanaRequest, Logger } from '@kbn/core/server';
-import { ObservabilityServerRouteRepository } from './get_global_observability_server_route_repository';
+import {
+ ObservabilityServerRouteRepository,
+ APIEndpoint,
+} from './get_global_observability_server_route_repository';
import { ObservabilityRequestHandlerContext } from '../types';
import { RegisterRoutesDependencies } from './register_routes';
import { ObservabilityConfig } from '..';
-export type { ObservabilityServerRouteRepository };
+export type { ObservabilityServerRouteRepository, APIEndpoint };
export interface ObservabilityRouteHandlerResources {
context: ObservabilityRequestHandlerContext;
diff --git a/x-pack/plugins/observability_solution/observability/server/services/index.ts b/x-pack/plugins/observability_solution/observability/server/services/index.ts
index 7c20d191440d67..840bac95ee48bb 100644
--- a/x-pack/plugins/observability_solution/observability/server/services/index.ts
+++ b/x-pack/plugins/observability_solution/observability/server/services/index.ts
@@ -13,9 +13,9 @@ import {
SavedObjectsClientContract,
} from '@kbn/core/server';
import { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server';
-import { concat } from 'lodash';
+import { flatten } from 'lodash';
-export const observabilityAlertDetailsContextRt = t.intersection([
+export const alertDetailsContextRt = t.intersection([
t.type({
alert_started_at: t.string,
}),
@@ -33,9 +33,7 @@ export const observabilityAlertDetailsContextRt = t.intersection([
}),
]);
-export type AlertDetailsContextualInsightsHandlerQuery = t.TypeOf<
- typeof observabilityAlertDetailsContextRt
->;
+export type AlertDetailsContextualInsightsHandlerQuery = t.TypeOf;
export interface AlertDetailsContextualInsight {
key: string;
@@ -77,11 +75,21 @@ export class AlertDetailsContextualInsightsService {
context: AlertDetailsContextualInsightsRequestContext,
query: AlertDetailsContextualInsightsHandlerQuery
): Promise {
- if (this.handlers.length === 0) return [];
+ if (this.handlers.length === 0) {
+ return [];
+ }
- return Promise.all(this.handlers.map((handler) => handler(context, query))).then((results) => {
- const [head, ...rest] = results;
- return concat(head, ...rest);
- });
+ const results = await Promise.all(
+ this.handlers.map(async (handler) => {
+ try {
+ return await handler(context, query);
+ } catch (error) {
+ console.error(`Error: Could not get alert context from handler`, error);
+ return [];
+ }
+ })
+ );
+
+ return flatten(results);
}
}
diff --git a/x-pack/plugins/observability_solution/observability/server/types.ts b/x-pack/plugins/observability_solution/observability/server/types.ts
index 76a209f3180780..8c298d239a53d5 100644
--- a/x-pack/plugins/observability_solution/observability/server/types.ts
+++ b/x-pack/plugins/observability_solution/observability/server/types.ts
@@ -19,6 +19,7 @@ export type {
ObservabilityRouteHandlerResources,
AbstractObservabilityServerRouteRepository,
ObservabilityServerRouteRepository,
+ APIEndpoint,
ObservabilityAPIReturnType,
} from './routes/types';
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts
index f5e9ca339e9e8c..84d604e54cc6e9 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts
@@ -12,6 +12,7 @@ import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plu
import { KibanaRequest } from '@kbn/core/server';
import { aiAssistantSimulatedFunctionCalling } from '../..';
import { flushBuffer } from '../../service/util/flush_buffer';
+import { observableIntoOpenAIStream } from '../../service/util/observable_into_openai_stream';
import { observableIntoStream } from '../../service/util/observable_into_stream';
import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';
import { screenContextRt, messageRt, functionRt } from '../runtime_types';
@@ -53,10 +54,13 @@ const chatCompleteInternalRt = t.intersection([
const chatCompletePublicRt = t.intersection([
chatCompleteBaseRt,
- t.type({
+ t.partial({
body: t.partial({
actions: t.array(functionRt),
}),
+ query: t.partial({
+ format: t.union([t.literal('default'), t.literal('openai')]),
+ }),
}),
]);
@@ -230,24 +234,32 @@ const publicChatCompleteRoute = createObservabilityAIAssistantServerRoute({
},
params: chatCompletePublicRt,
handler: async (resources): Promise => {
+ const { params, logger } = resources;
+
const {
body: { actions, ...restOfBody },
- } = resources.params;
- return observableIntoStream(
- await chatComplete({
- ...resources,
- params: {
- body: {
- ...restOfBody,
- screenContexts: [
- {
- actions,
- },
- ],
- },
+ query = {},
+ } = params;
+
+ const { format = 'default' } = query;
+
+ const response$ = await chatComplete({
+ ...resources,
+ params: {
+ body: {
+ ...restOfBody,
+ screenContexts: [
+ {
+ actions,
+ },
+ ],
},
- })
- );
+ },
+ });
+
+ return format === 'openai'
+ ? observableIntoOpenAIStream(response$, logger)
+ : observableIntoStream(response$);
},
});
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/observable_into_openai_stream.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/observable_into_openai_stream.ts
new file mode 100644
index 00000000000000..a5e6ef2d17c918
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/observable_into_openai_stream.ts
@@ -0,0 +1,91 @@
+/*
+ * 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 OpenAI from 'openai';
+import {
+ catchError,
+ concatMap,
+ endWith,
+ filter,
+ from,
+ ignoreElements,
+ map,
+ Observable,
+ of,
+} from 'rxjs';
+import { PassThrough } from 'stream';
+import {
+ BufferFlushEvent,
+ ChatCompletionChunkEvent,
+ StreamingChatResponseEventType,
+ StreamingChatResponseEventWithoutError,
+ TokenCountEvent,
+} from '../../../common/conversation_complete';
+
+export function observableIntoOpenAIStream(
+ source: Observable,
+ logger: Logger
+) {
+ const stream = new PassThrough();
+
+ source
+ .pipe(
+ filter(
+ (event): event is ChatCompletionChunkEvent =>
+ event.type === StreamingChatResponseEventType.ChatCompletionChunk
+ ),
+ map((event) => {
+ const chunk: OpenAI.ChatCompletionChunk = {
+ model: 'unknown',
+ choices: [
+ {
+ delta: {
+ content: event.message.content,
+ function_call: event.message.function_call,
+ },
+ finish_reason: null,
+ index: 0,
+ },
+ ],
+ created: new Date().getTime(),
+ id: event.id,
+ object: 'chat.completion.chunk',
+ };
+ return JSON.stringify(chunk);
+ }),
+ catchError((error) => {
+ return of(JSON.stringify({ error: { message: error.message } }));
+ }),
+ endWith('[DONE]'),
+ concatMap((line) => {
+ return from(
+ new Promise((resolve, reject) => {
+ stream.write(`data: ${line}\n\n`, (err) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve();
+ });
+ })
+ );
+ }),
+ ignoreElements()
+ )
+ .subscribe({
+ error: (error) => {
+ logger.error('Error writing stream');
+ logger.error(JSON.stringify(error));
+ stream.end(error);
+ },
+ complete: () => {
+ stream.end();
+ },
+ });
+
+ return stream;
+}
diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx
index 14a925d5502e2d..fb295d709a455d 100644
--- a/x-pack/plugins/observability_solution/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx
@@ -14,12 +14,14 @@ export interface SloResetConfirmationModalProps {
slo: SLOWithSummaryResponse | SLODefinitionResponse;
onCancel: () => void;
onConfirm: () => void;
+ isLoading?: boolean;
}
export function SloResetConfirmationModal({
slo,
onCancel,
onConfirm,
+ isLoading,
}: SloResetConfirmationModalProps) {
const { name } = slo;
return (
@@ -38,6 +40,7 @@ export function SloResetConfirmationModal({
})}
onCancel={onCancel}
onConfirm={onConfirm}
+ isLoading={isLoading}
>
{i18n.translate('xpack.slo.resetConfirmationModal.descriptionText', {
defaultMessage: 'Resetting this SLO will also regenerate the historical data.',
diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_instances.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_instances.ts
deleted file mode 100644
index 26367dcad3f6bc..00000000000000
--- a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_instances.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { GetSLOInstancesResponse } from '@kbn/slo-schema';
-import { useQuery } from '@tanstack/react-query';
-import { useKibana } from '../utils/kibana_react';
-import { sloKeys } from './query_key_factory';
-
-export interface UseFetchSloInstancesResponse {
- isInitialLoading: boolean;
- isLoading: boolean;
- isRefetching: boolean;
- isSuccess: boolean;
- isError: boolean;
- data: GetSLOInstancesResponse | undefined;
-}
-
-export function useFetchSloInstances({ sloId }: { sloId?: string }): UseFetchSloInstancesResponse {
- const { http } = useKibana().services;
-
- const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
- queryKey: sloKeys.detail(sloId),
- queryFn: async ({ signal }) => {
- try {
- const response = await http.get(
- `/internal/observability/slos/${sloId}/_instances`,
- {
- query: {},
- signal,
- }
- );
-
- return response;
- } catch (error) {
- // ignore error for retrieving slos
- }
- },
- keepPreviousData: true,
- enabled: Boolean(sloId),
- refetchOnWindowFocus: false,
- });
-
- return {
- data,
- isLoading,
- isInitialLoading,
- isRefetching,
- isSuccess,
- isError,
- };
-}
diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_reset_slo.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_reset_slo.ts
index e1a3a5a1dac1b8..18d09d58591455 100644
--- a/x-pack/plugins/observability_solution/slo/public/hooks/use_reset_slo.ts
+++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_reset_slo.ts
@@ -42,8 +42,10 @@ export function useResetSlo() {
}),
});
},
- onSuccess: (_data, { name }) => {
+ onSuccess: (_data, { name, id }) => {
queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });
+ queryClient.invalidateQueries({ queryKey: sloKeys.historicalSummaries(), exact: false });
+ queryClient.invalidateQueries({ queryKey: sloKeys.details(), exact: false });
toasts.addSuccess(
i18n.translate('xpack.slo.slo.reset.successNotification', {
defaultMessage: '{name} reset successfully',
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx
index ddf350c0df4781..61c681fcfa8532 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx
@@ -19,10 +19,12 @@ import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import React, { useCallback, useEffect, useState } from 'react';
import { paths } from '../../../../common/locators/paths';
import { SloDeleteConfirmationModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
+import { SloResetConfirmationModal } from '../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
import { useCapabilities } from '../../../hooks/use_capabilities';
import { useCloneSlo } from '../../../hooks/use_clone_slo';
import { useDeleteSlo } from '../../../hooks/use_delete_slo';
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
+import { useResetSlo } from '../../../hooks/use_reset_slo';
import { useKibana } from '../../../utils/kibana_react';
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
import { isApmIndicatorType } from '../../../utils/slo/indicator';
@@ -45,14 +47,17 @@ export function HeaderControl({ isLoading, slo }: Props) {
const hasApmReadCapabilities = capabilities.apm.show;
const { hasWriteCapabilities } = useCapabilities();
- const { isDeletingSlo, removeDeleteQueryParam } = useGetQueryParams();
+ const { isDeletingSlo, isResettingSlo, removeDeleteQueryParam, removeResetQueryParam } =
+ useGetQueryParams();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState(false);
const [isEditRuleFlyoutOpen, setIsEditRuleFlyoutOpen] = useState(false);
const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
+ const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false);
const { mutate: deleteSlo } = useDeleteSlo();
+ const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
const { data: rulesBySlo, refetchRules } = useFetchRulesForSlo({
sloIds: slo ? [slo.id] : undefined,
@@ -67,7 +72,10 @@ export function HeaderControl({ isLoading, slo }: Props) {
if (isDeletingSlo) {
setDeleteConfirmationModalOpen(true);
}
- }, [isDeletingSlo]);
+ if (isResettingSlo) {
+ setResetConfirmationModalOpen(true);
+ }
+ }, [isDeletingSlo, isResettingSlo]);
const onCloseRuleFlyout = () => {
setRuleFlyoutVisibility(false);
@@ -78,7 +86,7 @@ export function HeaderControl({ isLoading, slo }: Props) {
setRuleFlyoutVisibility(true);
};
- const { handleNavigateToRules, sloEditUrl, remoteDeleteUrl } = useSloActions({
+ const { handleNavigateToRules, sloEditUrl, remoteDeleteUrl, remoteResetUrl } = useSloActions({
slo,
rules,
setIsEditRuleFlyoutOpen,
@@ -119,13 +127,34 @@ export function HeaderControl({ isLoading, slo }: Props) {
setDeleteConfirmationModalOpen(false);
};
- const handleDeleteConfirm = async () => {
+ const handleDeleteConfirm = () => {
if (slo) {
deleteSlo({ id: slo.id, name: slo.name });
navigate(basePath.prepend(paths.slos));
}
};
+ const handleReset = () => {
+ if (!!remoteResetUrl) {
+ window.open(remoteResetUrl, '_blank');
+ } else {
+ setResetConfirmationModalOpen(true);
+ }
+ };
+
+ const handleResetConfirm = async () => {
+ if (slo) {
+ await resetSlo({ id: slo.id, name: slo.name });
+ removeResetQueryParam();
+ setResetConfirmationModalOpen(false);
+ }
+ };
+
+ const handleResetCancel = () => {
+ removeResetQueryParam();
+ setResetConfirmationModalOpen(false);
+ };
+
const navigate = useCallback(
(url: string) => setTimeout(() => navigateToUrl(url)),
[navigateToUrl]
@@ -263,6 +292,21 @@ export function HeaderControl({ isLoading, slo }: Props) {
defaultMessage: 'Delete',
})}
{showRemoteLinkIcon}
+ ,
+
+ {i18n.translate('xpack.slo.slo.item.actions.reset', {
+ defaultMessage: 'Reset',
+ })}
+ {showRemoteLinkIcon}
)}
/>
@@ -292,6 +336,15 @@ export function HeaderControl({ isLoading, slo }: Props) {
onConfirm={handleDeleteConfirm}
/>
) : null}
+
+ {slo && isResetConfirmationModalOpen ? (
+
+ ) : null}
>
);
}
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts
index 8e169e894c03f4..bea5080d91884b 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts
@@ -6,12 +6,13 @@
*/
import { ALL_VALUE } from '@kbn/slo-schema';
-import { useHistory, useLocation } from 'react-router-dom';
import { useCallback } from 'react';
+import { useHistory, useLocation } from 'react-router-dom';
export const INSTANCE_SEARCH_PARAM = 'instanceId';
export const REMOTE_NAME_PARAM = 'remoteName';
export const DELETE_SLO = 'delete';
+export const RESET_SLO = 'reset';
export function useGetQueryParams() {
const { search, pathname } = useLocation();
@@ -21,6 +22,7 @@ export function useGetQueryParams() {
const instanceId = searchParams.get(INSTANCE_SEARCH_PARAM);
const remoteName = searchParams.get(REMOTE_NAME_PARAM);
const deleteSlo = searchParams.get(DELETE_SLO);
+ const resetSlo = searchParams.get(RESET_SLO);
const removeDeleteQueryParam = useCallback(() => {
const qParams = new URLSearchParams(search);
@@ -35,10 +37,25 @@ export function useGetQueryParams() {
}
}, [deleteSlo, history, pathname, search]);
+ const removeResetQueryParam = useCallback(() => {
+ const qParams = new URLSearchParams(search);
+
+ // remote reset param from url after initial load
+ if (resetSlo === 'true') {
+ qParams.delete(RESET_SLO);
+ history.replace({
+ pathname,
+ search: qParams.toString(),
+ });
+ }
+ }, [resetSlo, history, pathname, search]);
+
return {
instanceId: !!instanceId && instanceId !== ALL_VALUE ? instanceId : undefined,
remoteName,
isDeletingSlo: deleteSlo === 'true',
removeDeleteQueryParam,
+ isResettingSlo: resetSlo === 'true',
+ removeResetQueryParam,
};
}
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_actions.ts b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_actions.ts
index 2129f0947361a8..44a6b8979ecd63 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_actions.ts
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_actions.ts
@@ -16,6 +16,7 @@ import { useKibana } from '../../../utils/kibana_react';
import {
createRemoteSloDeleteUrl,
createRemoteSloEditUrl,
+ createRemoteSloResetUrl,
} from '../../../utils/slo/remote_slo_urls';
export const useSloActions = ({
@@ -42,6 +43,7 @@ export const useSloActions = ({
sloEditUrl: '',
handleNavigateToRules: () => {},
remoteDeleteUrl: undefined,
+ remoteResetUrl: undefined,
sloDetailsUrl: '',
};
}
@@ -76,6 +78,7 @@ export const useSloActions = ({
);
const remoteDeleteUrl = createRemoteSloDeleteUrl(slo, spaceId);
+ const remoteResetUrl = createRemoteSloResetUrl(slo, spaceId);
const sloEditUrl = slo.remote
? createRemoteSloEditUrl(slo, spaceId)
@@ -85,6 +88,7 @@ export const useSloActions = ({
sloEditUrl,
handleNavigateToRules,
remoteDeleteUrl,
+ remoteResetUrl,
sloDetailsUrl: http.basePath.prepend(detailsUrl),
};
};
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_outdated_definitions/outdated_slo.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_outdated_definitions/outdated_slo.tsx
index 30e5c06cfbc07a..78fbe2bc8b5b0a 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slo_outdated_definitions/outdated_slo.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_outdated_definitions/outdated_slo.tsx
@@ -54,6 +54,7 @@ export function OutdatedSlo({ slo, onReset, onDelete }: OutdatedSloProps) {
const handleResetCancel = () => {
setResetConfirmationModalOpen(false);
};
+
return (
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item.tsx
index cf645bce217d07..ee5d75b6b651e6 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item.tsx
@@ -24,14 +24,16 @@ import {
import { ALL_VALUE, HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
import { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import React, { useState } from 'react';
-import { EditBurnRateRuleFlyout } from '../common/edit_burn_rate_rule_flyout';
import { SloDeleteConfirmationModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
+import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
+import { useResetSlo } from '../../../../hooks/use_reset_slo';
import { BurnRateRuleParams } from '../../../../typings';
import { useKibana } from '../../../../utils/kibana_react';
import { formatHistoricalData } from '../../../../utils/slo/chart_data_formatter';
import { useSloListActions } from '../../hooks/use_slo_list_actions';
import { useSloFormattedSummary } from '../../hooks/use_slo_summary';
import { BurnRateRuleFlyout } from '../common/burn_rate_rule_flyout';
+import { EditBurnRateRuleFlyout } from '../common/edit_burn_rate_rule_flyout';
import { SloCardItemActions } from './slo_card_item_actions';
import { SloCardItemBadges } from './slo_card_item_badges';
@@ -76,7 +78,9 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet
const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false);
const [isEditRuleFlyoutOpen, setIsEditRuleFlyoutOpen] = useState(false);
const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
+ const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false);
const [isDashboardAttachmentReady, setDashboardAttachmentReady] = useState(false);
+
const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value');
const { handleCreateRule, handleDeleteCancel, handleDeleteConfirm, handleAttachToDashboardSave } =
@@ -87,6 +91,17 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet
setIsAddRuleFlyoutOpen,
});
+ const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
+
+ const handleResetConfirm = async () => {
+ await resetSlo({ id: slo.id, name: slo.name });
+ setResetConfirmationModalOpen(false);
+ };
+
+ const handleResetCancel = () => {
+ setResetConfirmationModalOpen(false);
+ };
+
return (
<>
)}
@@ -156,6 +172,16 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet
onConfirm={handleDeleteConfirm}
/>
) : null}
+
+ {isResetConfirmationModalOpen ? (
+
+ ) : null}
+
{isDashboardAttachmentReady ? (
void;
setDeleteConfirmationModalOpen: (value: boolean) => void;
+ setResetConfirmationModalOpen: (value: boolean) => void;
setIsAddRuleFlyoutOpen: (value: boolean) => void;
setIsEditRuleFlyoutOpen: (value: boolean) => void;
setDashboardAttachmentReady: (value: boolean) => void;
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx
index 6ad7e054ed97e1..1361005be1be84 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx
@@ -24,6 +24,7 @@ import React, { useState } from 'react';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { paths } from '../../../../../common/locators/paths';
import { SloDeleteConfirmationModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
+import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
import { SloStatusBadge } from '../../../../components/slo/slo_status_badge';
import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge';
import { sloKeys } from '../../../../hooks/query_key_factory';
@@ -34,12 +35,14 @@ import { useFetchActiveAlerts } from '../../../../hooks/use_fetch_active_alerts'
import { useFetchHistoricalSummary } from '../../../../hooks/use_fetch_historical_summary';
import { useFetchRulesForSlo } from '../../../../hooks/use_fetch_rules_for_slo';
import { useGetFilteredRuleTypes } from '../../../../hooks/use_get_filtered_rule_types';
+import { useResetSlo } from '../../../../hooks/use_reset_slo';
import { useSpace } from '../../../../hooks/use_space';
import { useKibana } from '../../../../utils/kibana_react';
import { formatHistoricalData } from '../../../../utils/slo/chart_data_formatter';
import {
createRemoteSloDeleteUrl,
createRemoteSloEditUrl,
+ createRemoteSloResetUrl,
} from '../../../../utils/slo/remote_slo_urls';
import { SloRemoteBadge } from '../badges/slo_remote_badge';
import { SloRulesBadge } from '../badges/slo_rules_badge';
@@ -75,9 +78,13 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
const { hasWriteCapabilities } = useCapabilities();
const filteredRuleTypes = useGetFilteredRuleTypes();
const queryClient = useQueryClient();
+
const { mutate: deleteSlo } = useDeleteSlo();
+ const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
+
const [sloToAddRule, setSloToAddRule] = useState(undefined);
const [sloToDelete, setSloToDelete] = useState(undefined);
+ const [sloToReset, setSloToReset] = useState(undefined);
const handleDeleteConfirm = () => {
if (sloToDelete) {
@@ -90,6 +97,17 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
setSloToDelete(undefined);
};
+ const handleResetConfirm = async () => {
+ if (sloToReset) {
+ await resetSlo({ id: sloToReset.id, name: sloToReset.name });
+ setSloToReset(undefined);
+ }
+ };
+
+ const handleResetCancel = () => {
+ setSloToReset(undefined);
+ };
+
const handleSavedRule = async () => {
queryClient.invalidateQueries({ queryKey: sloKeys.rules(), exact: false });
};
@@ -242,6 +260,29 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
}
},
},
+ {
+ type: 'icon',
+ icon: 'refresh',
+ name: buildActionName(
+ i18n.translate('xpack.slo.item.actions.reset', {
+ defaultMessage: 'Reset',
+ })
+ ),
+ description: i18n.translate('xpack.slo.item.actions.reset', {
+ defaultMessage: 'Reset',
+ }),
+ 'data-test-subj': 'sloActionsReset',
+ enabled: (slo: SLOWithSummaryResponse) =>
+ (hasWriteCapabilities && !isRemote(slo)) || hasRemoteKibanaUrl(slo),
+ onClick: (slo: SLOWithSummaryResponse) => {
+ const remoteResetUrl = createRemoteSloResetUrl(slo, spaceId);
+ if (!!remoteResetUrl) {
+ window.open(remoteResetUrl, '_blank');
+ } else {
+ setSloToReset(slo);
+ }
+ },
+ },
];
const columns: Array> = [
@@ -441,6 +482,15 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
onConfirm={handleDeleteConfirm}
/>
) : null}
+
+ {sloToReset ? (
+
+ ) : null}
>
);
}
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx
index 2f6ce7da224e8c..a5de471bae883b 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx
@@ -30,6 +30,7 @@ interface Props {
isActionsPopoverOpen: boolean;
setIsActionsPopoverOpen: (value: boolean) => void;
setDeleteConfirmationModalOpen: (value: boolean) => void;
+ setResetConfirmationModalOpen: (value: boolean) => void;
setIsAddRuleFlyoutOpen: (value: boolean) => void;
setIsEditRuleFlyoutOpen: (value: boolean) => void;
setDashboardAttachmentReady?: (value: boolean) => void;
@@ -65,6 +66,7 @@ export function SloItemActions({
setIsAddRuleFlyoutOpen,
setIsEditRuleFlyoutOpen,
setDeleteConfirmationModalOpen,
+ setResetConfirmationModalOpen,
setDashboardAttachmentReady,
btnProps,
}: Props) {
@@ -77,12 +79,13 @@ export function SloItemActions({
const { hasWriteCapabilities } = useCapabilities();
const navigateToClone = useCloneSlo();
- const { handleNavigateToRules, sloEditUrl, remoteDeleteUrl, sloDetailsUrl } = useSloActions({
- slo,
- rules,
- setIsEditRuleFlyoutOpen,
- setIsActionsPopoverOpen,
- });
+ const { handleNavigateToRules, sloEditUrl, remoteDeleteUrl, remoteResetUrl, sloDetailsUrl } =
+ useSloActions({
+ slo,
+ rules,
+ setIsEditRuleFlyoutOpen,
+ setIsActionsPopoverOpen,
+ });
const handleClickActions = () => {
setIsActionsPopoverOpen(!isActionsPopoverOpen);
@@ -105,6 +108,15 @@ export function SloItemActions({
}
};
+ const handleReset = () => {
+ if (!!remoteResetUrl) {
+ window.open(remoteResetUrl, '_blank');
+ } else {
+ setResetConfirmationModalOpen(true);
+ setIsActionsPopoverOpen(false);
+ }
+ };
+
const handleCreateRule = () => {
setIsActionsPopoverOpen(false);
setIsAddRuleFlyoutOpen(true);
@@ -237,6 +249,20 @@ export function SloItemActions({
{i18n.translate('xpack.slo.item.actions.delete', { defaultMessage: 'Delete' })}
{showRemoteLinkIcon}
,
+ ,
+
+ {i18n.translate('xpack.slo.item.actions.reset', { defaultMessage: 'Reset' })}
+ {showRemoteLinkIcon}
+ ,
].concat(
!isDashboardContext ? (
{
+ await resetSlo({ id: slo.id, name: slo.name });
+ setResetConfirmationModalOpen(false);
+ };
+
+ const handleResetCancel = () => {
+ setResetConfirmationModalOpen(false);
+ };
return (
@@ -99,6 +111,7 @@ export function SloListItem({
setIsEditRuleFlyoutOpen={setIsEditRuleFlyoutOpen}
setIsActionsPopoverOpen={setIsActionsPopoverOpen}
setDeleteConfirmationModalOpen={setDeleteConfirmationModalOpen}
+ setResetConfirmationModalOpen={setResetConfirmationModalOpen}
/>
@@ -122,6 +135,15 @@ export function SloListItem({
onConfirm={handleDeleteConfirm}
/>
) : null}
+
+ {isResetConfirmationModalOpen ? (
+
+ ) : null}
);
}
diff --git a/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.ts b/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.ts
index 2e6c8bed91ba48..737a97fcfe73b7 100644
--- a/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.ts
+++ b/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.ts
@@ -45,6 +45,23 @@ export function createRemoteSloDeleteUrl(slo: SLOWithSummaryResponse, spaceId: s
return remoteUrl.toString();
}
+export function createRemoteSloResetUrl(slo: SLOWithSummaryResponse, spaceId: string = 'default') {
+ if (!slo.remote || slo.remote.kibanaUrl === '') {
+ return undefined;
+ }
+
+ const spacePath = spaceId !== 'default' ? `/s/${spaceId}` : '';
+ const detailsPath = paths.sloDetails(
+ slo.id,
+ ![slo.groupBy].flat().includes(ALL_VALUE) && slo.instanceId ? slo.instanceId : undefined
+ );
+
+ const remoteUrl = new URL(path.join(spacePath, detailsPath), slo.remote.kibanaUrl);
+ remoteUrl.searchParams.append('reset', 'true');
+
+ return remoteUrl.toString();
+}
+
export function createRemoteSloEditUrl(slo: SLOWithSummaryResponse, spaceId: string = 'default') {
if (!slo.remote || slo.remote.kibanaUrl === '') {
return undefined;
diff --git a/x-pack/plugins/search_playground/common/types.ts b/x-pack/plugins/search_playground/common/types.ts
index d2279633423adb..3ac8fe6e504366 100644
--- a/x-pack/plugins/search_playground/common/types.ts
+++ b/x-pack/plugins/search_playground/common/types.ts
@@ -19,6 +19,7 @@ export interface QuerySourceFields {
dense_vector_query_fields: ModelFields[];
bm25_query_fields: string[];
source_fields: string[];
+ skipped_fields: number;
}
export enum APIRoutes {
diff --git a/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx b/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx
index f1a9b835ac7727..4e5bb7f807900f 100644
--- a/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx
+++ b/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx
@@ -20,6 +20,12 @@ jest.mock('../../hooks/use_indices_fields', () => ({
bm25_query_fields: ['field1', 'field2'],
source_fields: ['context_field1', 'context_field2'],
},
+ index2: {
+ elser_query_fields: [],
+ dense_vector_query_fields: [],
+ bm25_query_fields: ['field1', 'field2'],
+ source_fields: ['context_field1', 'context_field2'],
+ },
},
}),
}));
@@ -36,6 +42,11 @@ const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
const methods = useForm({
values: {
indices: ['index1'],
+ docSize: 1,
+ sourceFields: {
+ index1: ['context_field1'],
+ index2: ['context_field2'],
+ },
},
});
return {children} ;
@@ -55,13 +66,14 @@ describe('EditContextFlyout component tests', () => {
});
it('calls onClose when the close button is clicked', () => {
- fireEvent.click(screen.getByTestId('euiFlyoutCloseButton'));
+ fireEvent.click(screen.getByRole('button', { name: 'Close' }));
expect(onCloseMock).toHaveBeenCalledTimes(1);
});
it('should see the context fields', async () => {
- expect(screen.getByTestId('contextFieldsSelectable')).toBeInTheDocument();
- expect(screen.getByTestId('contextFieldsSelectable')).toHaveTextContent(`context_field2`);
- expect(screen.getByTestId('contextFieldsSelectable')).toHaveTextContent(`context_field1`);
+ expect(screen.getByTestId('contextFieldsSelectable_index1')).toBeInTheDocument();
+ fireEvent.click(screen.getByTestId('contextFieldsSelectable_index1'));
+ expect(screen.getByRole('option', { name: 'context_field1' })).toBeInTheDocument();
+ expect(screen.getByRole('option', { name: 'context_field2' })).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.tsx b/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.tsx
index d960217c3eb9ba..38ee313dc1d878 100644
--- a/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.tsx
+++ b/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.tsx
@@ -18,11 +18,9 @@ import {
EuiLink,
EuiSpacer,
EuiText,
- EuiPanel,
- EuiAccordion,
- EuiSelectable,
EuiSelect,
- EuiSelectableOption,
+ EuiSuperSelect,
+ EuiFormRow,
} from '@elastic/eui';
import { useController, useFormContext } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
@@ -62,10 +60,10 @@ export const EditContextFlyout: React.FC = ({ onClose })
const [tempSourceFields, setTempSourceFields] = useState(sourceFields);
- const toggleSourceField = (index: string, f: EuiSelectableOption[]) => {
+ const updateSourceField = (index: string, field: string) => {
setTempSourceFields({
...tempSourceFields,
- [index]: f.filter(({ checked }) => checked === 'on').map(({ label }) => label),
+ [index]: [field],
});
usageTracker.click(AnalyticsEvents.editContextFieldToggled);
};
@@ -76,6 +74,7 @@ export const EditContextFlyout: React.FC = ({ onClose })
onChangeSize(docSize);
onClose();
};
+
const handleDocSizeChange = (e: React.ChangeEvent) => {
usageTracker.click(AnalyticsEvents.editContextDocSizeChanged);
setDocSize(Number(e.target.value));
@@ -119,71 +118,69 @@ export const EditContextFlyout: React.FC = ({ onClose })
-
+
-
-
-
-
-
+
-
-
- {Object.entries(fields).map(([index, group], i) => (
-
-
-
- {index}
-
- }
- >
-
- ({
- label: field,
- checked: tempSourceFields[index]?.includes(field) ? 'on' : undefined,
- }))}
- onChange={(newOptions) => toggleSourceField(index, newOptions)}
- listProps={{ bordered: false }}
- singleSelection="always"
- >
- {(list) => list}
-
-
-
-
- ))}
+
+
+
+
+
+
+
+
+
+
+
+ {Object.entries(fields).map(([index, group]) => (
+
+
+ ({
+ value: field,
+ inputDisplay: field,
+ }))}
+ valueOfSelected={tempSourceFields[index]?.[0]}
+ onChange={(value) => updateSourceField(index, value)}
+ />
+
+
+ ))}
+
+
diff --git a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx
index 4af61de670ab75..410989eaf52ad4 100644
--- a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx
+++ b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx
@@ -18,6 +18,13 @@ jest.mock('../../hooks/use_indices_fields', () => ({
elser_query_fields: [],
dense_vector_query_fields: [],
bm25_query_fields: ['field1', 'field2'],
+ skipped_fields: 1,
+ },
+ index2: {
+ elser_query_fields: [],
+ dense_vector_query_fields: [],
+ bm25_query_fields: ['field1', 'field2'],
+ skipped_fields: 0,
},
},
}),
@@ -34,7 +41,7 @@ jest.mock('../../hooks/use_usage_tracker', () => ({
const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
const methods = useForm({
values: {
- indices: ['index1'],
+ indices: ['index1', 'index2'],
},
});
return {children} ;
@@ -61,7 +68,19 @@ describe('ViewQueryFlyout component tests', () => {
it('should see the view elasticsearch query', async () => {
expect(screen.getByTestId('ViewElasticsearchQueryResult')).toBeInTheDocument();
expect(screen.getByTestId('ViewElasticsearchQueryResult')).toHaveTextContent(
- `{ "retriever": { "standard": { "query": { "multi_match": { "query": "{query}", "fields": [ "field1" ] } } } } }`
+ `{ "retriever": { "rrf": { "retrievers": [ { "standard": { "query": { "multi_match": { "query": "{query}", "fields": [ "field1" ] } } } }, { "standard": { "query": { "multi_match": { "query": "{query}", "fields": [ "field1" ] } } } } ] } } }`
);
});
+
+ it('displays query fields and indicates hidden fields', () => {
+ expect(screen.getByTestId('queryFieldsSelectable_index1')).toBeInTheDocument();
+ expect(screen.getByTestId('queryFieldsSelectable_index2')).toBeInTheDocument();
+
+ // Check if hidden fields indicator is shown
+ expect(screen.getByTestId('skipped_fields_index1')).toBeInTheDocument();
+ expect(screen.getByTestId('skipped_fields_index1')).toHaveTextContent('1 fields are hidden.');
+
+ // Check if hidden fields indicator is shown
+ expect(screen.queryByTestId('skipped_fields_index2')).not.toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx
index 8b5c01df5103b8..621fecfaf72d48 100644
--- a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx
+++ b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx
@@ -22,12 +22,16 @@ import {
EuiSelectableOption,
EuiText,
EuiTitle,
+ EuiCheckbox,
+ EuiLink,
+ EuiIcon,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useEffect, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { AnalyticsEvents } from '../../analytics/constants';
+import { docLinks } from '../../../common/doc_links';
import { useIndicesFields } from '../../hooks/use_indices_fields';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { ChatForm, ChatFormFields, IndicesQuerySourceFields } from '../../types';
@@ -157,7 +161,7 @@ export const ViewQueryFlyout: React.FC = ({ onClose }) =>
-
+
= ({ onClose }) =>
/>
- {Object.entries(fields).map(([index, group], i) => (
+ {Object.entries(fields).map(([index, group]) => (
{index}
@@ -181,24 +185,70 @@ export const ViewQueryFlyout: React.FC = ({ onClose }) =>
({
- label: typeof field === 'string' ? field : field.field,
- checked: isQueryFieldSelected(
+ ].map((field, idx) => {
+ const checked = isQueryFieldSelected(
index,
typeof field === 'string' ? field : field.field
- )
- ? 'on'
- : undefined,
- }))}
+ );
+ return {
+ label: typeof field === 'string' ? field : field.field,
+ prepend: (
+ {}}
+ />
+ ),
+ checked: checked ? 'on' : undefined,
+ };
+ })}
+ listProps={{
+ bordered: false,
+ showIcons: false,
+ }}
onChange={(newOptions) => updateFields(index, newOptions)}
- listProps={{ bordered: false }}
>
{(list) => list}
+ {group.skipped_fields > 0 && (
+ <>
+
+
+
+
+
+ {` `}
+
+
+
+
+
+
+
+
+
+ >
+ )}
diff --git a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx
index 20e7551511eb27..f3b19e8d4360ec 100644
--- a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx
+++ b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx
@@ -43,6 +43,7 @@ describe.skip('useSourceIndicesFields Hook', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: ['field1'],
+ skipped_fields: 0,
},
};
@@ -144,6 +145,7 @@ describe.skip('useSourceIndicesFields Hook', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -196,6 +198,7 @@ describe.skip('useSourceIndicesFields Hook', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
diff --git a/x-pack/plugins/search_playground/public/providers/playground_provider.tsx b/x-pack/plugins/search_playground/public/providers/playground_provider.tsx
index e9e6e7d2a80008..b03ebaffb7da0d 100644
--- a/x-pack/plugins/search_playground/public/providers/playground_provider.tsx
+++ b/x-pack/plugins/search_playground/public/providers/playground_provider.tsx
@@ -24,7 +24,7 @@ export const PlaygroundProvider: FC>
const form = useForm({
defaultValues: {
prompt: 'You are an assistant for question-answering tasks.',
- doc_size: 5,
+ doc_size: 3,
source_fields: [],
indices: defaultValues?.indices || [],
},
diff --git a/x-pack/plugins/search_playground/public/utils/create_query.test.ts b/x-pack/plugins/search_playground/public/utils/create_query.test.ts
index 58b289f2caf035..282326f0991d2b 100644
--- a/x-pack/plugins/search_playground/public/utils/create_query.test.ts
+++ b/x-pack/plugins/search_playground/public/utils/create_query.test.ts
@@ -23,6 +23,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -55,6 +56,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -92,6 +94,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -100,6 +103,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -133,6 +137,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -141,6 +146,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -196,6 +202,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -228,6 +235,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -257,6 +265,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: ['content', 'title'],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -265,6 +274,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -321,6 +331,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: ['content', 'title'],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -329,6 +340,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -390,6 +402,7 @@ describe('create_query', () => {
],
bm25_query_fields: ['content', 'title'],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -398,6 +411,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -435,6 +449,7 @@ describe('create_query', () => {
],
bm25_query_fields: ['content', 'title'],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -487,6 +502,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -509,6 +525,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -524,6 +541,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -549,6 +567,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
index2: {
elser_query_fields: [
@@ -564,6 +583,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -582,6 +602,7 @@ describe('create_query', () => {
],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -595,6 +616,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: ['title', 'text', 'content'],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -610,6 +632,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: ['unknown1', 'unknown2'],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -659,6 +682,7 @@ describe('create_query', () => {
'url_path_dir2',
'url_path_dir1',
],
+ skipped_fields: 0,
},
};
@@ -674,6 +698,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
},
};
@@ -691,6 +716,7 @@ describe('create_query', () => {
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: ['non_suggested_field'],
+ skipped_fields: 0,
},
};
diff --git a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts
index 6896b0f6025e53..048c0a8fd83e1a 100644
--- a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts
+++ b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts
@@ -48,6 +48,7 @@ describe('fetch_query_source_fields', () => {
indices: ['workplace_index'],
},
],
+ skipped_fields: 8,
source_fields: ['metadata.summary', 'metadata.rolePermissions', 'text', 'metadata.name'],
},
workplace_index2: {
@@ -58,6 +59,7 @@ describe('fetch_query_source_fields', () => {
'metadata.name',
],
dense_vector_query_fields: [],
+ skipped_fields: 8,
elser_query_fields: [
{
field: 'content_vector.tokens',
@@ -110,6 +112,7 @@ describe('fetch_query_source_fields', () => {
},
],
elser_query_fields: [],
+ skipped_fields: 30,
source_fields: [
'page_content_key',
'title',
@@ -150,11 +153,13 @@ describe('fetch_query_source_fields', () => {
},
],
source_fields: ['body_content', 'headings', 'title'],
+ skipped_fields: 4,
},
});
});
it('should return the correct fields for a document first index', () => {
+ // Skips the nested dense vector field.
expect(
parseFieldsCapabilities(DENSE_VECTOR_DOCUMENT_FIRST_FIELD_CAPS, [
{
@@ -174,14 +179,7 @@ describe('fetch_query_source_fields', () => {
'metadata.summary',
'metadata.content',
],
- dense_vector_query_fields: [
- {
- field: 'passages.vector.predicted_value',
- model_id: '.multilingual-e5-small',
- nested: true,
- indices: ['workplace_index_nested'],
- },
- ],
+ dense_vector_query_fields: [],
elser_query_fields: [],
source_fields: [
'metadata.category',
@@ -193,6 +191,7 @@ describe('fetch_query_source_fields', () => {
'metadata.summary',
'metadata.content',
],
+ skipped_fields: 18,
},
});
});
diff --git a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts
index 84cd8db481cefb..fb2eba4835cb28 100644
--- a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts
+++ b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts
@@ -7,7 +7,7 @@
import { FieldCapsResponse, SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
-import { get } from 'lodash';
+import { get, has } from 'lodash';
import { IndicesQuerySourceFields } from '../types';
export const fetchFields = async (
@@ -45,10 +45,23 @@ export const fetchFields = async (
return parseFieldsCapabilities(fieldCapabilities, indexDocs);
};
+const INFERENCE_MODEL_FIELD_REGEXP = /\.predicted_value|\.tokens/;
+
+const hasModelField = (field: string, indexDoc: any, nestedField: string | false) => {
+ if (field.match(INFERENCE_MODEL_FIELD_REGEXP)) {
+ const path = nestedField ? field.replace(`${nestedField}.`, `${nestedField}[0].`) : field;
+ return has(
+ indexDoc.doc,
+ `_source.${[path.replace(INFERENCE_MODEL_FIELD_REGEXP, '.model_id')]}`
+ );
+ }
+ return false;
+};
+
const getModelField = (field: string, indexDoc: any, nestedField: string | false) => {
// If the field is nested, we need to get the first occurrence as its an array
const path = nestedField ? field.replace(`${nestedField}.`, `${nestedField}[0].`) : field;
- return get(indexDoc.doc, `_source.${[path.replace(/\.predicted_value|\.tokens/, '.model_id')]}`);
+ return get(indexDoc.doc, `_source.${[path.replace(INFERENCE_MODEL_FIELD_REGEXP, '.model_id')]}`);
};
const isFieldNested = (field: string, fieldCapsResponse: FieldCapsResponse) => {
@@ -83,6 +96,7 @@ export const parseFieldsCapabilities = (
dense_vector_query_fields: [],
bm25_query_fields: [],
source_fields: [],
+ skipped_fields: 0,
};
return acc;
}, {});
@@ -106,25 +120,41 @@ export const parseFieldsCapabilities = (
if ('rank_features' in field || 'sparse_vector' in field) {
const nestedField = isFieldNested(fieldKey, fieldCapsResponse);
- const elserModelField = {
- field: fieldKey,
- model_id: getModelField(fieldKey, indexDoc, nestedField),
- nested: !!isFieldNested(fieldKey, fieldCapsResponse),
- indices: indicesPresentIn,
- };
- acc[index].elser_query_fields.push(elserModelField);
+ if (!nestedField) {
+ const elserModelField = {
+ field: fieldKey,
+ model_id: getModelField(fieldKey, indexDoc, nestedField),
+ nested: !!isFieldNested(fieldKey, fieldCapsResponse),
+ indices: indicesPresentIn,
+ };
+ acc[index].elser_query_fields.push(elserModelField);
+ } else {
+ acc[index].skipped_fields++;
+ }
} else if ('dense_vector' in field) {
const nestedField = isFieldNested(fieldKey, fieldCapsResponse);
- const denseVectorField = {
- field: fieldKey,
- model_id: getModelField(fieldKey, indexDoc, nestedField),
- nested: !!nestedField,
- indices: indicesPresentIn,
- };
- acc[index].dense_vector_query_fields.push(denseVectorField);
+
+ // Check if the dense vector field has a model_id associated with it
+ // skip this field if has no model associated with it
+ // and the vectors were embedded outside of stack
+ if (hasModelField(fieldKey, indexDoc, nestedField) && !nestedField) {
+ const denseVectorField = {
+ field: fieldKey,
+ model_id: getModelField(fieldKey, indexDoc, nestedField),
+ nested: !!nestedField,
+ indices: indicesPresentIn,
+ };
+ acc[index].dense_vector_query_fields.push(denseVectorField);
+ } else {
+ acc[index].skipped_fields++;
+ }
} else if ('text' in field && field.text.searchable && shouldIgnoreField(fieldKey)) {
acc[index].bm25_query_fields.push(fieldKey);
acc[index].source_fields.push(fieldKey);
+ } else {
+ if (fieldKey !== '_id' && fieldKey !== '_index' && fieldKey !== '_type') {
+ acc[index].skipped_fields++;
+ }
}
}
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/get_all_integrations/get_all_integrations_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/get_all_integrations/get_all_integrations_route.ts
new file mode 100644
index 00000000000000..0798808365e12b
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/get_all_integrations/get_all_integrations_route.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { Integration } from '../model/integrations';
+
+export interface GetAllIntegrationsResponse {
+ integrations: Integration[];
+}
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/index.ts
index 63a824a430c6ec..1c503733669552 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/index.ts
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/index.ts
@@ -5,7 +5,10 @@
* 2.0.
*/
+export * from './get_all_integrations/get_all_integrations_route';
+
export * from './get_installed_integrations/get_installed_integrations_route';
export * from './urls';
+export * from './model/integrations';
export * from './model/installed_integrations';
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/model/integrations.ts b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/model/integrations.ts
new file mode 100644
index 00000000000000..d1ddeb0ffa0572
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/model/integrations.ts
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+// -------------------------------------------------------------------------------------------------
+// Fleet Package Integration
+
+/**
+ * Information about a Fleet integration including info about its package.
+ *
+ * @example
+ * {
+ * package_name: 'aws',
+ * package_title: 'AWS',
+ * integration_name: 'cloudtrail',
+ * integration_title: 'AWS CloudTrail',
+ * latest_package_version: '1.2.3',
+ * is_installed: false
+ * is_enabled: false
+ * }
+ *
+ * @example
+ * {
+ * package_name: 'aws',
+ * package_title: 'AWS',
+ * integration_name: 'cloudtrail',
+ * integration_title: 'AWS CloudTrail',
+ * latest_package_version: '1.16.1',
+ * installed_package_version: '1.16.1',
+ * is_installed: true
+ * is_enabled: false
+ * }
+ *
+ * @example
+ * {
+ * package_name: 'system',
+ * package_title: 'System',
+ * latest_package_version: '2.0.1',
+ * installed_package_version: '1.13.0',
+ * is_installed: true
+ * is_enabled: true
+ * }
+ *
+ */
+export interface Integration {
+ /**
+ * Name is a unique package id within a given cluster.
+ * There can't be 2 or more different packages with the same name.
+ * @example 'aws'
+ */
+ package_name: string;
+
+ /**
+ * Title is a user-friendly name of the package that we show in the UI.
+ * @example 'AWS'
+ */
+ package_title: string;
+
+ /**
+ * Whether the package is installed
+ */
+ is_installed: boolean;
+
+ /**
+ * Whether this integration is enabled
+ */
+ is_enabled: boolean;
+
+ /**
+ * Version of the latest available package. Semver-compatible.
+ * @example '1.2.3'
+ */
+ latest_package_version: string;
+
+ /**
+ * Version of the installed package. Semver-compatible.
+ * @example '1.2.3'
+ */
+ installed_package_version?: string;
+
+ /**
+ * Name identifies an integration within its package.
+ * Undefined when package name === integration name. This indicates that it's the only integration
+ * within this package.
+ * @example 'cloudtrail'
+ * @example undefined
+ */
+ integration_name?: string;
+
+ /**
+ * Title is a user-friendly name of the integration that we show in the UI.
+ * Undefined when package name === integration name. This indicates that it's the only integration
+ * within this package.
+ * @example 'AWS CloudTrail'
+ * @example undefined
+ */
+ integration_title?: string;
+}
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/urls.ts b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/urls.ts
index b1216d855284e6..04cf30bc509fe1 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/urls.ts
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations/urls.ts
@@ -7,5 +7,7 @@
import { INTERNAL_DETECTION_ENGINE_URL as INTERNAL_URL } from '../../../constants';
+export const GET_ALL_INTEGRATIONS_URL = `${INTERNAL_URL}/fleet/integrations/all` as const;
+
export const GET_INSTALLED_INTEGRATIONS_URL =
`${INTERNAL_URL}/fleet/integrations/installed` as const;
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts
index fc29093779c753..d05a2723375342 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts
@@ -53,11 +53,11 @@ import {
MaxSignals,
ThreatArray,
SetupGuide,
+ RelatedIntegrationArray,
RuleObjectId,
RuleSignatureId,
IsRuleImmutable,
RuleSource,
- RelatedIntegrationArray,
RequiredFieldArray,
RuleQuery,
IndexPatternArray,
@@ -136,6 +136,7 @@ export const BaseDefaultableFields = z.object({
max_signals: MaxSignals.optional(),
threat: ThreatArray.optional(),
setup: SetupGuide.optional(),
+ related_integrations: RelatedIntegrationArray.optional(),
});
export type BaseCreateProps = z.infer;
@@ -163,7 +164,6 @@ export const ResponseFields = z.object({
created_at: z.string().datetime(),
created_by: z.string(),
revision: z.number().int().min(0),
- related_integrations: RelatedIntegrationArray,
required_fields: RequiredFieldArray,
execution_summary: RuleExecutionSummary.optional(),
});
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml
index 4a2279f7a7c8d1..dfb3bfb738a5c2 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml
@@ -131,6 +131,9 @@ components:
setup:
$ref: './common_attributes.schema.yaml#/components/schemas/SetupGuide'
+ related_integrations:
+ $ref: './common_attributes.schema.yaml#/components/schemas/RelatedIntegrationArray'
+
BaseCreateProps:
x-inline: true
allOf:
@@ -178,13 +181,11 @@ components:
revision:
type: integer
minimum: 0
- # NOTE: For now, Related Integrations and Required Fields are
+ # NOTE: For now, Required Fields are
# supported for prebuilt rules only. We don't want to allow users to edit these 3
# fields via the API. If we added them to baseParams.defaultable, they would
# become a part of the request schema as optional fields. This is why we add them
# here, in order to add them only to the response schema.
- related_integrations:
- $ref: './common_attributes.schema.yaml#/components/schemas/RelatedIntegrationArray'
required_fields:
$ref: './common_attributes.schema.yaml#/components/schemas/RequiredFieldArray'
execution_summary:
diff --git a/x-pack/plugins/security_solution/public/common/mock/create_react_query_wrapper.tsx b/x-pack/plugins/security_solution/public/common/mock/create_react_query_wrapper.tsx
new file mode 100644
index 00000000000000..42377ecca87c8e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/mock/create_react_query_wrapper.tsx
@@ -0,0 +1,25 @@
+/*
+ * 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 { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+export function createReactQueryWrapper(): React.FC {
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ // Turn retries off, otherwise we won't be able to test errors
+ retry: false,
+ },
+ },
+ });
+
+ // eslint-disable-next-line react/display-name
+ return ({ children }) => (
+ {children}
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/common/mock/index.ts b/x-pack/plugins/security_solution/public/common/mock/index.ts
index d4cc1185846bb1..3be928b9dcc1fd 100644
--- a/x-pack/plugins/security_solution/public/common/mock/index.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/index.ts
@@ -19,3 +19,4 @@ export * from './test_providers';
export * from './timeline_results';
export * from './utils';
export * from './create_store';
+export * from './create_react_query_wrapper';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.ts
index f0dbf5dd6899e3..eadc0a1cb2d41c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.ts
@@ -5,13 +5,49 @@
* 2.0.
*/
-import type { GetInstalledIntegrationsResponse } from '../../../../../common/api/detection_engine/fleet_integrations';
import type {
+ GetAllIntegrationsResponse,
+ GetInstalledIntegrationsResponse,
+} from '../../../../../common/api/detection_engine/fleet_integrations';
+import type {
+ FetchAllIntegrationsArgs,
FetchInstalledIntegrationsArgs,
IFleetIntegrationsApiClient,
} from '../api_client_interface';
export const fleetIntegrationsApi: jest.Mocked = {
+ fetchAllIntegrations: jest
+ .fn, [FetchAllIntegrationsArgs]>()
+ .mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'o365',
+ package_title: 'Microsoft 365',
+ latest_package_version: '1.2.0',
+ installed_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ {
+ package_name: 'atlassian_bitbucket',
+ package_title: 'Atlassian Bitbucket',
+ latest_package_version: '1.0.1',
+ installed_package_version: '1.0.1',
+ integration_name: 'audit',
+ integration_title: 'Audit Logs',
+ is_installed: true,
+ is_enabled: true,
+ },
+ {
+ package_name: 'system',
+ package_title: 'System',
+ latest_package_version: '1.6.4',
+ installed_package_version: '1.6.4',
+ is_installed: true,
+ is_enabled: true,
+ },
+ ],
+ }),
fetchInstalledIntegrations: jest
.fn, [FetchInstalledIntegrationsArgs]>()
.mockResolvedValue({
diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts
index 192683f84fc468..812daf5cf21046 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts
@@ -5,16 +5,31 @@
* 2.0.
*/
-import type { GetInstalledIntegrationsResponse } from '../../../../common/api/detection_engine/fleet_integrations';
-import { GET_INSTALLED_INTEGRATIONS_URL } from '../../../../common/api/detection_engine/fleet_integrations';
+import type {
+ GetAllIntegrationsResponse,
+ GetInstalledIntegrationsResponse,
+} from '../../../../common/api/detection_engine/fleet_integrations';
+import {
+ GET_ALL_INTEGRATIONS_URL,
+ GET_INSTALLED_INTEGRATIONS_URL,
+} from '../../../../common/api/detection_engine/fleet_integrations';
import { KibanaServices } from '../../../common/lib/kibana';
import type {
+ FetchAllIntegrationsArgs,
FetchInstalledIntegrationsArgs,
IFleetIntegrationsApiClient,
} from './api_client_interface';
export const fleetIntegrationsApi: IFleetIntegrationsApiClient = {
+ fetchAllIntegrations: (args: FetchAllIntegrationsArgs): Promise => {
+ return http().fetch(GET_ALL_INTEGRATIONS_URL, {
+ method: 'GET',
+ version: '1',
+ signal: args.signal,
+ });
+ },
+
fetchInstalledIntegrations: (
args: FetchInstalledIntegrationsArgs
): Promise => {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.ts
index ba847ae39b97ba..8b2610a098efcb 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.ts
@@ -5,9 +5,19 @@
* 2.0.
*/
-import type { GetInstalledIntegrationsResponse } from '../../../../common/api/detection_engine/fleet_integrations';
+import type {
+ GetInstalledIntegrationsResponse,
+ GetAllIntegrationsResponse,
+} from '../../../../common/api/detection_engine/fleet_integrations';
export interface IFleetIntegrationsApiClient {
+ /**
+ * Fetch all integrations with installed and enabled statuses
+ *
+ * @throws An error if response is not OK
+ */
+ fetchAllIntegrations(args: FetchAllIntegrationsArgs): Promise;
+
/**
* Fetch all installed integrations.
* @throws An error if response is not OK
@@ -17,6 +27,13 @@ export interface IFleetIntegrationsApiClient {
): Promise;
}
+export interface FetchAllIntegrationsArgs {
+ /**
+ * Optional signal for cancelling the request.
+ */
+ signal?: AbortSignal;
+}
+
export interface FetchInstalledIntegrationsArgs {
/**
* Array of Fleet packages to filter for.
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/default_related_integration.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/default_related_integration.ts
new file mode 100644
index 00000000000000..bc8063c3db9ca4
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/default_related_integration.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const DEFAULT_RELATED_INTEGRATION = { package: '', version: '' };
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/index.ts
new file mode 100644
index 00000000000000..0b169487aa490c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { RelatedIntegrations } from './related_integrations';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/integration_status_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/integration_status_badge.tsx
new file mode 100644
index 00000000000000..0aa77fa9d39fd3
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/integration_status_badge.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiBadge } from '@elastic/eui';
+import * as i18n from './translations';
+
+interface IntegrationStatusBadgeProps {
+ isInstalled: boolean;
+ isEnabled: boolean;
+}
+
+export function IntegrationStatusBadge({
+ isInstalled,
+ isEnabled,
+}: IntegrationStatusBadgeProps): JSX.Element {
+ const color = isEnabled ? 'success' : isInstalled ? 'primary' : undefined;
+ const statusText = isEnabled
+ ? i18n.INTEGRATION_INSTALLED_AND_ENABLED
+ : isInstalled
+ ? i18n.INTEGRATION_INSTALLED_AND_DISABLED
+ : i18n.INTEGRATION_NOT_INSTALLED;
+
+ return (
+
+ {statusText}
+
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integration_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integration_field.tsx
new file mode 100644
index 00000000000000..c24220923441bb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integration_field.tsx
@@ -0,0 +1,241 @@
+/*
+ * 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 { ChangeEvent } from 'react';
+import React, { useCallback, useMemo } from 'react';
+import { capitalize } from 'lodash';
+import semver from 'semver';
+import { css } from '@emotion/css';
+import type { EuiComboBoxOptionOption } from '@elastic/eui';
+import {
+ EuiTextTruncate,
+ EuiButtonIcon,
+ EuiComboBox,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+} from '@elastic/eui';
+import type { FieldHook } from '../../../../shared_imports';
+import type { Integration, RelatedIntegration } from '../../../../../common/api/detection_engine';
+import { useIntegrations } from '../../../../detections/components/rules/related_integrations/use_integrations';
+import { IntegrationStatusBadge } from './integration_status_badge';
+import { DEFAULT_RELATED_INTEGRATION } from './default_related_integration';
+import * as i18n from './translations';
+
+interface RelatedIntegrationItemFormProps {
+ field: FieldHook;
+ relatedIntegrations: RelatedIntegration[];
+ onRemove: () => void;
+}
+
+export function RelatedIntegrationField({
+ field,
+ relatedIntegrations,
+ onRemove,
+}: RelatedIntegrationItemFormProps): JSX.Element {
+ const { data: integrations, isInitialLoading } = useIntegrations();
+ const [integrationOptions, selectedIntegrationOptions] = useMemo(() => {
+ const currentKey = getKey(field.value.package, field.value.integration);
+ const relatedIntegrationsButCurrent = relatedIntegrations.filter(
+ (ri) => getKey(ri.package, ri.integration) !== currentKey
+ );
+ const unusedIntegrations = filterOutUsedIntegrations(
+ integrations ?? [],
+ relatedIntegrationsButCurrent
+ );
+
+ const options = unusedIntegrations.map(transformIntegrationToOption);
+ const fallbackSelectedOption =
+ field.value.package.length > 0
+ ? {
+ key: currentKey,
+ label: `${capitalize(field.value.package)} ${field.value.integration ?? ''}`,
+ }
+ : undefined;
+ const selectedOption =
+ options.find((option) => option.key === currentKey) ?? fallbackSelectedOption;
+
+ return [options, selectedOption ? [selectedOption] : []];
+ }, [integrations, field.value, relatedIntegrations]);
+
+ const [packageErrorMessage, versionErrorMessage] = useMemo(() => {
+ const packagePath = `${field.path}.package`;
+ const versionPath = `${field.path}.version`;
+
+ return [
+ field.errors.find((err) => 'path' in err && err.path === packagePath),
+ field.errors.find((err) => 'path' in err && err.path === versionPath),
+ ];
+ }, [field.path, field.errors]);
+
+ const handleIntegrationChange = useCallback(
+ ([changedSelectedOption]: Array>) =>
+ field.setValue({
+ package: changedSelectedOption?.value?.package_name ?? '',
+ integration: changedSelectedOption?.value?.integration_name,
+ version: changedSelectedOption?.value
+ ? calculateRelevantSemver(changedSelectedOption.value)
+ : '',
+ }),
+ [field]
+ );
+
+ const handleVersionChange = useCallback(
+ (e: ChangeEvent) =>
+ field.setValue((oldValue) => ({
+ ...oldValue,
+ version: e.target.value,
+ })),
+ [field]
+ );
+
+ const hasError = Boolean(packageErrorMessage) || Boolean(versionErrorMessage);
+ const isLastField = relatedIntegrations.length === 1;
+ const isLastEmptyField = isLastField && field.value.package === '';
+ const handleRemove = useCallback(() => {
+ if (isLastField) {
+ field.setValue(DEFAULT_RELATED_INTEGRATION);
+ return;
+ }
+
+ onRemove();
+ }, [onRemove, field, isLastField]);
+
+ return (
+
+
+
+
+ options={integrationOptions}
+ renderOption={renderIntegrationOption}
+ selectedOptions={selectedIntegrationOptions}
+ singleSelection
+ isLoading={isInitialLoading}
+ isDisabled={!integrations}
+ onChange={handleIntegrationChange}
+ fullWidth
+ aria-label={i18n.RELATED_INTEGRATION_ARIA_LABEL}
+ isInvalid={Boolean(packageErrorMessage)}
+ data-test-subj="relatedIntegrationComboBox"
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const ROW_OVERFLOW_FIX_STYLE = css`
+ overflow: hidden;
+`;
+
+/**
+ * Minimum width has been determined empirically like that
+ * semver value like `^1.2.3` doesn't overflow
+ */
+const MIN_WIDTH_VERSION_CONSTRAIN_STYLE = css`
+ min-width: 150px;
+`;
+
+function filterOutUsedIntegrations(
+ integrations: Integration[],
+ relatedIntegrations: RelatedIntegration[]
+): Integration[] {
+ const usedIntegrationsSet = new Set(
+ relatedIntegrations.map((ri) => getKey(ri.package, ri.integration))
+ );
+
+ return integrations?.filter(
+ (i) => !usedIntegrationsSet.has(getKey(i.package_name, i.integration_name))
+ );
+}
+
+function transformIntegrationToOption(
+ integration: Integration
+): EuiComboBoxOptionOption {
+ const integrationTitle = integration.integration_title ?? integration.package_title;
+ const label = integration.is_enabled
+ ? i18n.INTEGRATION_ENABLED(integrationTitle)
+ : integration.is_installed
+ ? i18n.INTEGRATION_DISABLED(integrationTitle)
+ : integrationTitle;
+
+ return {
+ key: getKey(integration.package_name, integration.integration_name),
+ label,
+ value: integration,
+ color: integration.is_enabled ? 'success' : integration.is_installed ? 'primary' : undefined,
+ };
+}
+
+function getKey(packageName: string | undefined, integrationName: string | undefined): string {
+ return `${packageName ?? ''}${integrationName ?? ''}`;
+}
+
+function renderIntegrationOption(
+ option: EuiComboBoxOptionOption
+): JSX.Element | string {
+ const { label, value } = option;
+
+ if (!value) {
+ return label;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+function calculateRelevantSemver(integration: Integration): string {
+ if (!integration.installed_package_version) {
+ return `^${integration.latest_package_version}`;
+ }
+
+ // In some rare cases users may install a prerelease integration version.
+ // We need to build constraint on the latest stable version and
+ // it's supposed `latest_package_version` is the latest stable version.
+ if (semver.gt(integration.installed_package_version, integration.latest_package_version)) {
+ return `^${integration.latest_package_version}`;
+ }
+
+ return `^${integration.installed_package_version}`;
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integration_field_row.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integration_field_row.tsx
new file mode 100644
index 00000000000000..de549be3fae027
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integration_field_row.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useCallback } from 'react';
+import type { RelatedIntegration } from '../../../../../common/api/detection_engine';
+import type { ArrayItem, FieldConfig } from '../../../../shared_imports';
+import { FIELD_TYPES, UseField } from '../../../../shared_imports';
+import { DEFAULT_RELATED_INTEGRATION } from './default_related_integration';
+import { RelatedIntegrationField } from './related_integration_field';
+import { validateRelatedIntegration } from './validate_related_integration';
+
+interface RelatedIntegrationFieldRowProps {
+ item: ArrayItem;
+ relatedIntegrations: RelatedIntegration[];
+ removeItem: (id: number) => void;
+}
+
+export function RelatedIntegrationFieldRow({
+ item,
+ relatedIntegrations,
+ removeItem,
+}: RelatedIntegrationFieldRowProps): JSX.Element {
+ const handleRemove = useCallback(() => removeItem(item.id), [removeItem, item.id]);
+
+ return (
+
+ );
+}
+
+const RELATED_INTEGRATION_FIELD_CONFIG: FieldConfig = {
+ type: FIELD_TYPES.JSON,
+ validations: [{ validator: validateRelatedIntegration }],
+ defaultValue: DEFAULT_RELATED_INTEGRATION,
+};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.test.tsx
new file mode 100644
index 00000000000000..21fa15c3587193
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.test.tsx
@@ -0,0 +1,856 @@
+/*
+ * 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 {
+ screen,
+ render,
+ act,
+ fireEvent,
+ waitFor,
+ waitForElementToBeRemoved,
+} from '@testing-library/react';
+import type { RelatedIntegration } from '../../../../../common/api/detection_engine';
+import { FIELD_TYPES, Form, useForm } from '../../../../shared_imports';
+import { createReactQueryWrapper } from '../../../../common/mock';
+import { fleetIntegrationsApi } from '../../../fleet_integrations/api/__mocks__';
+import { RelatedIntegrations } from './related_integrations';
+
+// must match to the import in rules/related_integrations/use_integrations.tsx
+jest.mock('../../../fleet_integrations/api');
+jest.mock('../../../../common/lib/kibana', () => ({
+ useKibana: jest.fn().mockReturnValue({
+ services: {
+ docLinks: {
+ links: {
+ securitySolution: {
+ ruleUiAdvancedParams: 'http://link-to-docs',
+ },
+ },
+ },
+ },
+ }),
+}));
+
+const RELATED_INTEGRATION_ROW = 'relatedIntegrationRow';
+const COMBO_BOX_TOGGLE_BUTTON_TEST_ID = 'comboBoxToggleListButton';
+const COMBO_BOX_SELECTION_TEST_ID = 'euiComboBoxPill';
+const COMBO_BOX_CLEAR_BUTTON_TEST_ID = 'comboBoxClearButton';
+const VERSION_INPUT_TEST_ID = 'relatedIntegrationVersionDependency';
+const REMOVE_INTEGRATION_ROW_BUTTON_TEST_ID = 'relatedIntegrationRemove';
+
+describe('RelatedIntegrations form part', () => {
+ beforeEach(() => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+ });
+
+ it('renders related integrations legend', () => {
+ render( );
+
+ expect(screen.getByText('Related integrations')).toBeVisible();
+ });
+
+ describe('visual representation', () => {
+ it('shows package title when integration title is not set', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+
+ expect(screen.getByTestId(COMBO_BOX_SELECTION_TEST_ID)).toHaveTextContent('Package A');
+ });
+
+ it('shows integration title when package and integration titles are set', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ integration_name: 'integration-a',
+ integration_title: 'Integration A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+
+ expect(screen.getByTestId(COMBO_BOX_SELECTION_TEST_ID)).toHaveTextContent('Integration A');
+ });
+
+ it.each([
+ [
+ 'Not installed',
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ [
+ 'Installed: Disabled',
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ installed_package_version: '1.2.0',
+ latest_package_version: '1.2.0',
+ is_installed: true,
+ is_enabled: false,
+ },
+ ],
+ [
+ 'Installed: Enabled',
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ installed_package_version: '1.2.0',
+ latest_package_version: '1.2.0',
+ is_installed: true,
+ is_enabled: true,
+ },
+ ],
+ ])('shows integration status "%s" in combo box popover', async (status, integrationData) => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [integrationData],
+ });
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await showEuiComboBoxOptions(screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID));
+
+ expect(screen.getByRole('option')).toHaveTextContent(status);
+ });
+
+ it.each([
+ [
+ 'Package A',
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ [
+ 'Package A: Disabled',
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ installed_package_version: '1.2.0',
+ latest_package_version: '1.2.0',
+ is_installed: true,
+ is_enabled: false,
+ },
+ ],
+ [
+ 'Package A: Enabled',
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ installed_package_version: '1.2.0',
+ latest_package_version: '1.2.0',
+ is_installed: true,
+ is_enabled: true,
+ },
+ ],
+ ])(
+ 'shows integration name with its status "%s" when selected in combo box',
+ async (nameWithStatus, integrationData) => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [integrationData],
+ });
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+
+ expect(screen.getByTestId(COMBO_BOX_SELECTION_TEST_ID)).toHaveTextContent(
+ new RegExp(`^${nameWithStatus}$`)
+ );
+ }
+ );
+
+ it('shows integration version constraint corresponding to the latest package version when integration is NOT installed', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+
+ expect(screen.getByTestId(VERSION_INPUT_TEST_ID)).toHaveValue('^1.2.0');
+ });
+
+ it('shows integration version constraint corresponding to the installed package version when integration is installed', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ installed_package_version: '1.1.0',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+
+ expect(screen.getByTestId(VERSION_INPUT_TEST_ID)).toHaveValue('^1.1.0');
+ });
+
+ it('shows saved earlier related integrations', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ {
+ package_name: 'package-b',
+ package_title: 'Package B',
+ integration_name: 'integration-a',
+ integration_title: 'Integration A',
+ latest_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ];
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+
+ await waitForIntegrationsToBeLoaded();
+
+ const visibleIntegrations = screen.getAllByTestId(COMBO_BOX_SELECTION_TEST_ID);
+ const visibleVersionInputs = screen.getAllByTestId(VERSION_INPUT_TEST_ID);
+
+ expect(visibleIntegrations[0]).toHaveTextContent('Package A');
+ expect(visibleVersionInputs[0]).toHaveValue('1.2.3');
+
+ expect(visibleIntegrations[1]).toHaveTextContent('Integration A');
+ expect(visibleVersionInputs[1]).toHaveValue('3.2.1');
+ });
+
+ it('shows saved earlier related integrations when there is no matching package found', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [], // package-a and package-b don't exist
+ });
+
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ];
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+
+ await waitForIntegrationsToBeLoaded();
+
+ const visibleIntegrations = screen.getAllByTestId(COMBO_BOX_SELECTION_TEST_ID);
+ const visibleVersionInputs = screen.getAllByTestId(VERSION_INPUT_TEST_ID);
+
+ expect(visibleIntegrations[0]).toHaveTextContent('Package-a');
+ expect(visibleVersionInputs[0]).toHaveValue('1.2.3');
+
+ expect(visibleIntegrations[1]).toHaveTextContent('Package-b integration-a');
+ expect(visibleVersionInputs[1]).toHaveValue('3.2.1');
+ });
+
+ it('shows saved earlier related integrations when API failed', async () => {
+ // suppress expected API error messages
+ jest.spyOn(console, 'error').mockReturnValue();
+
+ fleetIntegrationsApi.fetchAllIntegrations.mockRejectedValue(new Error('some error'));
+
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ];
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+
+ await waitForIntegrationsToBeLoaded();
+
+ const visibleIntegrations = screen.getAllByTestId(COMBO_BOX_SELECTION_TEST_ID);
+ const visibleVersionInputs = screen.getAllByTestId(VERSION_INPUT_TEST_ID);
+
+ expect(visibleIntegrations[0]).toHaveTextContent('Package-a');
+ expect(visibleVersionInputs[0]).toHaveValue('1.2.3');
+
+ expect(visibleIntegrations[1]).toHaveTextContent('Package-b integration-a');
+ expect(visibleVersionInputs[1]).toHaveValue('3.2.1');
+ });
+ });
+
+ describe('valid form submitting', () => {
+ it('returns undefined when no integrations are selected', async () => {
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: undefined,
+ isValid: true,
+ });
+ });
+
+ it('returns empty integrations when submitting not filled form', async () => {
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await addRelatedIntegrationRow();
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [
+ { package: '', version: '' },
+ { package: '', version: '' },
+ ],
+ isValid: true,
+ });
+ });
+
+ it('returns a mix of filled and empty integrations', async () => {
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await addRelatedIntegrationRow();
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getAllByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID)[1],
+ });
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [
+ { package: '', version: '' },
+ { package: 'package-a', version: '^1.0.0' },
+ { package: '', version: '' },
+ ],
+ isValid: true,
+ });
+ });
+
+ it('returns an empty integration after clearing selected integration', async () => {
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+ await clearEuiComboBoxSelection({
+ clearButton: screen.getByTestId(COMBO_BOX_CLEAR_BUTTON_TEST_ID),
+ });
+
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ package: '', version: '' }],
+ isValid: true,
+ });
+ });
+
+ it('returns a selected integration', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ optionIndex: 0,
+ });
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ integration: undefined, package: 'package-a', version: '^1.2.0' }],
+ isValid: true,
+ });
+ });
+
+ it('returns a selected integration with version constraint modified', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.2.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ optionIndex: 0,
+ });
+ await setVersion({ input: screen.getByTestId(VERSION_INPUT_TEST_ID), value: '1.0.0' });
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ integration: undefined, package: 'package-a', version: '1.0.0' }],
+ isValid: true,
+ });
+ });
+
+ it('returns saved earlier integrations', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ {
+ package_name: 'package-b',
+ package_title: 'Package B',
+ integration_name: 'integration-a',
+ integration_title: 'Integration A',
+ latest_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ];
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [
+ { package: 'package-a', integration: undefined, version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ],
+ isValid: true,
+ });
+ });
+
+ it('returns a saved earlier integration when there is no matching package found', async () => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [],
+ });
+
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '1.2.3' },
+ ];
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ integration: undefined, package: 'package-a', version: '1.2.3' }],
+ isValid: true,
+ });
+ });
+
+ it('returns a saved earlier integration when API failed', async () => {
+ // suppress expected API error messages
+ jest.spyOn(console, 'error').mockReturnValue();
+
+ fleetIntegrationsApi.fetchAllIntegrations.mockRejectedValue(new Error('some error'));
+
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '^1.2.3' },
+ ];
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ integration: undefined, package: 'package-a', version: '^1.2.3' }],
+ isValid: true,
+ });
+ });
+ });
+
+ describe('validation errors', () => {
+ it('shows an error when version constraint is invalid', async () => {
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+ await setVersion({ input: screen.getByTestId(VERSION_INPUT_TEST_ID), value: '100' });
+
+ expect(screen.getByTestId(RELATED_INTEGRATION_ROW)).toHaveTextContent(
+ 'Version constraint is invalid'
+ );
+ });
+ });
+
+ describe('removing an item', () => {
+ describe('when there is more than one item', () => {
+ it('removes just added item', async () => {
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await addRelatedIntegrationRow();
+ await removeLastRelatedIntegrationRow();
+
+ expect(screen.getAllByTestId(RELATED_INTEGRATION_ROW)).toHaveLength(1);
+ });
+
+ it('removes just added item after integration has been selected', async () => {
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getAllByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID).at(-1)!,
+ });
+ await removeLastRelatedIntegrationRow();
+
+ expect(screen.getAllByTestId(RELATED_INTEGRATION_ROW)).toHaveLength(1);
+ });
+
+ it('submits an empty integration when just added integrations removed', async () => {
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: screen.getAllByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID).at(-1)!,
+ });
+ await removeLastRelatedIntegrationRow();
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ package: '', version: '' }],
+ isValid: true,
+ });
+ });
+ });
+
+ describe('sticky last form row', () => {
+ it('does not remove the last item', async () => {
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await removeLastRelatedIntegrationRow();
+
+ expect(screen.getAllByTestId(RELATED_INTEGRATION_ROW)).toHaveLength(1);
+ });
+
+ it('disables remove button after clicking remove button on the last item', async () => {
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await removeLastRelatedIntegrationRow();
+
+ expect(screen.getByTestId(REMOVE_INTEGRATION_ROW_BUTTON_TEST_ID)).toBeDisabled();
+ });
+
+ it('clears selected integration when clicking remove the last form row button', async () => {
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: getLastByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+ await removeLastRelatedIntegrationRow();
+
+ expect(screen.queryByTestId(COMBO_BOX_SELECTION_TEST_ID)).not.toBeInTheDocument();
+ });
+
+ it('submits an empty integration after clicking remove the last form row button', async () => {
+ const handleSubmit = jest.fn();
+
+ render( , { wrapper: createReactQueryWrapper() });
+
+ await addRelatedIntegrationRow();
+ await selectFirstEuiComboBoxOption({
+ comboBoxToggleButton: getLastByTestId(COMBO_BOX_TOGGLE_BUTTON_TEST_ID),
+ });
+ await removeLastRelatedIntegrationRow();
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ package: '', version: '' }],
+ isValid: true,
+ });
+ });
+
+ it('submits an empty integration after previously saved integrations were removed', async () => {
+ const initialRelatedIntegrations: RelatedIntegration[] = [
+ { package: 'package-a', version: '^1.2.3' },
+ ];
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: createReactQueryWrapper(),
+ });
+
+ await waitForIntegrationsToBeLoaded();
+ await removeLastRelatedIntegrationRow();
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith({
+ data: [{ package: '', version: '' }],
+ isValid: true,
+ });
+ });
+ });
+ });
+});
+
+interface TestFormProps {
+ initialState?: RelatedIntegration[];
+ onSubmit?: (args: { data: RelatedIntegration[]; isValid: boolean }) => void;
+}
+
+function TestForm({ initialState, onSubmit }: TestFormProps): JSX.Element {
+ const { form } = useForm({
+ options: { stripEmptyFields: false },
+ schema: {
+ relatedIntegrationsField: {
+ type: FIELD_TYPES.JSON,
+ },
+ },
+ defaultValue: {
+ relatedIntegrationsField: initialState,
+ },
+ onSubmit: async (formData, isValid) =>
+ onSubmit?.({ data: formData.relatedIntegrationsField, isValid }),
+ });
+
+ return (
+
+ );
+}
+
+function getLastByTestId(testId: string): HTMLElement {
+ // getAllByTestId throws an error when there are no `testId` elements found
+ return screen.getAllByTestId(testId).at(-1)!;
+}
+
+function waitForIntegrationsToBeLoaded(): Promise {
+ return waitForElementToBeRemoved(screen.queryAllByRole('progressbar'));
+}
+
+function addRelatedIntegrationRow(): Promise {
+ return act(async () => {
+ fireEvent.click(screen.getByText('Add integration'));
+ });
+}
+
+function removeLastRelatedIntegrationRow(): Promise {
+ return act(async () => {
+ const lastRemoveButton = screen.getAllByTestId(REMOVE_INTEGRATION_ROW_BUTTON_TEST_ID).at(-1);
+
+ if (!lastRemoveButton) {
+ throw new Error(`There are no "${REMOVE_INTEGRATION_ROW_BUTTON_TEST_ID}" found`);
+ }
+
+ fireEvent.click(lastRemoveButton);
+ });
+}
+
+function showEuiComboBoxOptions(comboBoxToggleButton: HTMLElement): Promise {
+ fireEvent.click(comboBoxToggleButton);
+
+ return waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+}
+
+function selectEuiComboBoxOption({
+ comboBoxToggleButton,
+ optionIndex,
+}: {
+ comboBoxToggleButton: HTMLElement;
+ optionIndex: number;
+}): Promise {
+ return act(async () => {
+ await showEuiComboBoxOptions(comboBoxToggleButton);
+
+ fireEvent.click(screen.getAllByRole('option')[optionIndex]);
+ });
+}
+
+function clearEuiComboBoxSelection({ clearButton }: { clearButton: HTMLElement }): Promise {
+ return act(async () => {
+ fireEvent.click(clearButton);
+ });
+}
+
+function selectFirstEuiComboBoxOption({
+ comboBoxToggleButton,
+}: {
+ comboBoxToggleButton: HTMLElement;
+}): Promise {
+ return selectEuiComboBoxOption({ comboBoxToggleButton, optionIndex: 0 });
+}
+
+function setVersion({ input, value }: { input: HTMLInputElement; value: string }): Promise {
+ return act(async () => {
+ fireEvent.input(input, {
+ target: { value },
+ });
+ });
+}
+
+function submitForm(): Promise {
+ return act(async () => {
+ fireEvent.click(screen.getByText('Submit'));
+ });
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx
new file mode 100644
index 00000000000000..a57ce5fe8cd7b0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+import { UseArray, useFormData } from '../../../../shared_imports';
+import { RelatedIntegrationsHelpInfo } from './related_integrations_help_info';
+import { RelatedIntegrationFieldRow } from './related_integration_field_row';
+import * as i18n from './translations';
+
+interface RelatedIntegrationsProps {
+ path: string;
+ dataTestSubj?: string;
+}
+
+export function RelatedIntegrations({ path, dataTestSubj }: RelatedIntegrationsProps): JSX.Element {
+ const label = (
+ <>
+ {i18n.RELATED_INTEGRATIONS_LABEL}
+
+ >
+ );
+ const [formData] = useFormData();
+
+ return (
+
+ {({ items, addItem, removeItem }) => (
+
+ {i18n.OPTIONAL}
+
+ }
+ labelType="legend"
+ fullWidth
+ data-test-subj={dataTestSubj}
+ hasChildLabel={false}
+ >
+ <>
+
+ {items.map((item) => (
+
+
+
+ ))}
+
+ {items.length > 0 && }
+
+ {i18n.ADD_INTEGRATION}
+
+ >
+
+ )}
+
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx
new file mode 100644
index 00000000000000..b694d17a804355
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations_help_info.tsx
@@ -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 React from 'react';
+import { useToggle } from 'react-use';
+import { EuiLink, EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { useKibana } from '../../../../common/lib/kibana';
+
+/**
+ * Theme doesn't expose width variables. Using provided size variables will require
+ * multiplying it by another magic constant.
+ *
+ * 320px width looks
+ * like a [commonly used width in EUI](https://github.com/search?q=repo%3Aelastic%2Feui%20320&type=code).
+ */
+const POPOVER_WIDTH = 320;
+
+export function RelatedIntegrationsHelpInfo(): JSX.Element {
+ const [isPopoverOpen, togglePopover] = useToggle(false);
+ const { docLinks } = useKibana().services;
+
+ const button = (
+
+ );
+
+ return (
+
+
+
+
+
+ ),
+ semverLink: (
+
+
+
+ ),
+ }}
+ />
+
+
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/translations.ts
new file mode 100644
index 00000000000000..2645298783f5cc
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/translations.ts
@@ -0,0 +1,136 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const RELATED_INTEGRATIONS_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.fieldRelatedIntegrationsLabel',
+ {
+ defaultMessage: 'Related integrations',
+ }
+);
+
+export const OPTIONAL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.optionalText',
+ {
+ defaultMessage: 'Optional',
+ }
+);
+
+export const RELATED_INTEGRATION_FIELDS_HELP_TEXT = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.helpText',
+ {
+ defaultMessage: 'Select an integration and correct a version constraint if necessary.',
+ }
+);
+
+export const RELATED_INTEGRATION_ARIA_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.relatedIntegrationAriaLabel',
+ {
+ defaultMessage: 'Integrations selector',
+ }
+);
+
+export const RELATED_INTEGRATION_VERSION_DEPENDENCY_ARIA_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.relatedIntegrationVersionDependencyAriaLabel',
+ {
+ defaultMessage: 'Related integration version constraint',
+ }
+);
+
+export const RELATED_INTEGRATION_VERSION_DEPENDENCY_PLACEHOLDER = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.relatedIntegrationVersionDependencyPlaceholder',
+ {
+ defaultMessage: 'Semver',
+ }
+);
+
+export const REMOVE_RELATED_INTEGRATION_BUTTON_ARIA_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.removeRelatedIntegrationButtonAriaLabel',
+ {
+ defaultMessage: 'Remove related integration',
+ }
+);
+
+export const ADD_INTEGRATION = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.addIntegration',
+ {
+ defaultMessage: 'Add integration',
+ }
+);
+
+export const INTEGRATION_VERSION = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.integrationVersion',
+ {
+ defaultMessage: 'Version',
+ }
+);
+
+export const INTEGRATION_REQUIRED = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.validation.integrationRequired',
+ {
+ defaultMessage: 'Integration must be selected',
+ }
+);
+
+export const VERSION_DEPENDENCY_REQUIRED = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.validation.versionRequired',
+ {
+ defaultMessage: 'Version constraint must be specified',
+ }
+);
+
+export const VERSION_DEPENDENCY_INVALID = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.validation.versionInvalid',
+ {
+ defaultMessage:
+ 'Version constraint is invalid. Only tilde, caret or plain version supported e.g. ~1.2.3, ^1.2.3 or 1.2.3.',
+ }
+);
+
+export const INTEGRATION_NOT_INSTALLED = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.notInstalledText',
+ {
+ defaultMessage: 'Not installed',
+ }
+);
+
+export const INTEGRATION_INSTALLED_AND_DISABLED = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.installedDisabledText',
+ {
+ defaultMessage: 'Installed: Disabled',
+ }
+);
+
+export const INTEGRATION_INSTALLED_AND_ENABLED = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.installedEnabledText',
+ {
+ defaultMessage: 'Installed: Enabled',
+ }
+);
+
+export const INTEGRATION_DISABLED = (integrationTitle: string) =>
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.integrationDisabledText',
+ {
+ defaultMessage: '{integrationTitle}: Disabled',
+ values: {
+ integrationTitle,
+ },
+ }
+ );
+
+export const INTEGRATION_ENABLED = (integrationTitle: string) =>
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.ruleDescription.relatedIntegrations.integrationEnabledText',
+ {
+ defaultMessage: '{integrationTitle}: Enabled',
+ values: {
+ integrationTitle,
+ },
+ }
+ );
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/validate_related_integration.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/validate_related_integration.test.ts
new file mode 100644
index 00000000000000..d991101cfce4b2
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/validate_related_integration.test.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { RelatedIntegration } from '../../../../../common/api/detection_engine';
+import type { ValidationFuncArg } from '../../../../shared_imports';
+import { validateRelatedIntegration } from './validate_related_integration';
+
+describe('validateRelatedIntegration', () => {
+ describe('with successful outcome', () => {
+ it.each([
+ ['simple package version dependency', { package: 'some-package', version: '1.2.3' }],
+ ['caret package version dependency', { package: 'some-package', version: '^1.2.3' }],
+ ['tilde package version dependency', { package: 'some-package', version: '~1.2.3' }],
+ ])(`validates %s`, (_, relatedIntegration) => {
+ const arg = {
+ value: relatedIntegration,
+ } as ValidationFuncArg;
+
+ const result = validateRelatedIntegration(arg);
+
+ expect(result).toBeUndefined();
+ });
+
+ it('validates empty package as a valid related integration', () => {
+ const relatedIntegration = { package: '', version: '1.2.3' };
+ const arg = {
+ value: relatedIntegration,
+ path: 'form.path.to.field',
+ } as ValidationFuncArg;
+
+ const result = validateRelatedIntegration(arg);
+
+ expect(result).toBeUndefined();
+ });
+
+ it('ignores version when package is empty', () => {
+ const relatedIntegration = { package: '', version: 'invalid' };
+ const arg = {
+ value: relatedIntegration,
+ path: 'form.path.to.field',
+ } as ValidationFuncArg;
+
+ const result = validateRelatedIntegration(arg);
+
+ expect(result).toBeUndefined();
+ });
+ });
+
+ describe('with unsuccessful outcome', () => {
+ it('validates empty version', () => {
+ const relatedIntegration = { package: 'some-package', version: '' };
+ const arg = {
+ value: relatedIntegration,
+ path: 'form.path.to.field',
+ } as ValidationFuncArg;
+
+ const result = validateRelatedIntegration(arg);
+
+ expect(result).toMatchObject({
+ code: 'ERR_FIELD_MISSING',
+ path: 'form.path.to.field.version',
+ });
+ });
+
+ it('validates version with white spaces', () => {
+ const relatedIntegration = { package: 'some-package', version: ' ' };
+ const arg = {
+ value: relatedIntegration,
+ path: 'form.path.to.field',
+ } as ValidationFuncArg;
+
+ const result = validateRelatedIntegration(arg);
+
+ expect(result).toMatchObject({
+ code: 'ERR_FIELD_MISSING',
+ path: 'form.path.to.field.version',
+ });
+ });
+
+ it.each([
+ ['invalid format version', { package: 'some-package', version: '^1.2.' }],
+ ['unexpected version spaces', { package: 'some-package', version: ' ~ 1.2.3' }],
+ ])(`validates %s`, (_, relatedIntegration) => {
+ const arg = {
+ value: relatedIntegration,
+ path: 'form.path.to.field',
+ } as ValidationFuncArg;
+
+ const result = validateRelatedIntegration(arg);
+
+ expect(result).toMatchObject({
+ code: 'ERR_FIELD_FORMAT',
+ path: 'form.path.to.field.version',
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/validate_related_integration.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/validate_related_integration.ts
new file mode 100644
index 00000000000000..3cbf2eff33b3dc
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/validate_related_integration.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { RelatedIntegration } from '../../../../../common/api/detection_engine';
+import type { FormData, ERROR_CODE, ValidationFunc } from '../../../../shared_imports';
+import * as i18n from './translations';
+
+export function validateRelatedIntegration(
+ ...args: Parameters>
+): ReturnType> | undefined {
+ const [{ value, path }] = args;
+
+ // It allows to submit empty fields for better UX
+ // When integration isn't selected version shouldn't be validated
+ if (value.package.trim().length === 0) {
+ return;
+ }
+
+ if (value.version.trim().length === 0) {
+ return {
+ code: 'ERR_FIELD_MISSING',
+ path: `${path}.version`,
+ message: i18n.VERSION_DEPENDENCY_REQUIRED,
+ };
+ }
+
+ if (!SEMVER_PATTERN.test(value.version)) {
+ return {
+ code: 'ERR_FIELD_FORMAT',
+ path: `${path}.version`,
+ message: i18n.VERSION_DEPENDENCY_INVALID,
+ };
+ }
+}
+
+const SEMVER_PATTERN = /^(\~|\^)?\d+\.\d+\.\d+$/;
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 22e43dc31acb87..de34718ef050f6 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
@@ -5,26 +5,22 @@
* 2.0.
*/
-import React from 'react';
-import { mount } from 'enzyme';
-
-import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
-
+import React, { useEffect, useState } from 'react';
+import { screen, fireEvent, render, within, act, waitFor } from '@testing-library/react';
+import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types';
import { StepDefineRule, aggregatableFields } from '.';
import { mockBrowserFields } from '../../../../common/containers/source/mock';
import { useRuleFromTimeline } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline';
-import { fireEvent, render, within } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
-import { useRuleForms } from '../../pages/form';
-import { stepActionsDefaultValue } from '../../../rule_creation/components/step_rule_actions';
-import {
- defaultSchedule,
- stepAboutDefaultValue,
- stepDefineDefaultValue,
-} from '../../../../detections/pages/detection_engine/rules/utils';
-import type { FormHook } from '../../../../shared_imports';
+import { schema as defineRuleSchema } from './schema';
+import { stepDefineDefaultValue } from '../../../../detections/pages/detection_engine/rules/utils';
+import type { FormSubmitHandler } from '../../../../shared_imports';
+import { useForm } from '../../../../shared_imports';
import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types';
+import { fleetIntegrationsApi } from '../../../fleet_integrations/api/__mocks__';
+// Mocks integrations
+jest.mock('../../../fleet_integrations/api');
jest.mock('../../../../common/components/query_bar', () => {
return {
QueryBar: jest.fn(({ filterQuery }) => {
@@ -279,37 +275,261 @@ test('aggregatableFields with aggregatable: true', function () {
const mockUseRuleFromTimeline = useRuleFromTimeline as jest.Mock;
const onOpenTimeline = jest.fn();
+
+const COMBO_BOX_TOGGLE_BUTTON_TEST_ID = 'comboBoxToggleListButton';
+const VERSION_INPUT_TEST_ID = 'relatedIntegrationVersionDependency';
+
describe('StepDefineRule', () => {
- const TestComp = ({
- setFormRef,
- ruleType = stepDefineDefaultValue.ruleType,
- }: {
- setFormRef: (form: FormHook) => void;
- ruleType?: Type;
- }) => {
- const { defineStepForm, eqlOptionsSelected, setEqlOptionsSelected } = useRuleForms({
- defineStepDefault: { ...stepDefineDefaultValue, ruleType },
- aboutStepDefault: stepAboutDefaultValue,
- scheduleStepDefault: defaultSchedule,
- actionsStepDefault: stepActionsDefaultValue,
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseRuleFromTimeline.mockReturnValue({ onOpenTimeline, loading: false });
+ });
+
+ it('renders correctly', () => {
+ render( , {
+ wrapper: TestProviders,
+ });
+
+ expect(screen.getByTestId('stepDefineRule')).toBeDefined();
+ });
+
+ describe('related integrations', () => {
+ beforeEach(() => {
+ fleetIntegrationsApi.fetchAllIntegrations.mockResolvedValue({
+ integrations: [
+ {
+ package_name: 'package-a',
+ package_title: 'Package A',
+ latest_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
+ ],
+ });
+ });
+
+ it('submits form without selected related integrations', async () => {
+ const initialState = {
+ index: ['test-index'],
+ queryBar: {
+ query: { query: '*:*', language: 'kuery' },
+ filters: [],
+ saved_id: null,
+ },
+ };
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: TestProviders,
+ });
+
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith(
+ expect.not.objectContaining({
+ relatedIntegrations: expect.anything(),
+ }),
+ true
+ );
+ });
+
+ it('submits saved early related integrations', async () => {
+ const initialState = {
+ index: ['test-index'],
+ queryBar: {
+ query: { query: '*:*', language: 'kuery' },
+ filters: [],
+ saved_id: null,
+ },
+ relatedIntegrations: [
+ { package: 'package-a', version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ],
+ };
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: TestProviders,
+ });
+
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ relatedIntegrations: [
+ { package: 'package-a', version: '1.2.3' },
+ { package: 'package-b', integration: 'integration-a', version: '3.2.1' },
+ ],
+ }),
+ true
+ );
+ });
+
+ it('submits a selected related integration', async () => {
+ const initialState = {
+ index: ['test-index'],
+ queryBar: {
+ query: { query: '*:*', language: 'kuery' },
+ filters: [],
+ saved_id: null,
+ },
+ relatedIntegrations: undefined,
+ };
+ const handleSubmit = jest.fn();
+
+ render( , {
+ wrapper: TestProviders,
+ });
+
+ await addRelatedIntegrationRow();
+ await selectEuiComboBoxOption({
+ comboBoxToggleButton: within(screen.getByTestId('relatedIntegrations')).getByTestId(
+ COMBO_BOX_TOGGLE_BUTTON_TEST_ID
+ ),
+ optionIndex: 0,
+ });
+ await setVersion({ input: screen.getByTestId(VERSION_INPUT_TEST_ID), value: '1.2.3' });
+
+ await submitForm();
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ expect(handleSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ relatedIntegrations: [{ package: 'package-a', version: '1.2.3' }],
+ }),
+ true
+ );
+ });
+ });
+
+ describe('handleSetRuleFromTimeline', () => {
+ it('updates KQL query correctly', () => {
+ const kqlQuery = {
+ index: ['.alerts-security.alerts-default', 'logs-*', 'packetbeat-*'],
+ queryBar: {
+ filters: [],
+ query: {
+ query: 'host.name:*',
+ language: 'kuery',
+ },
+ saved_id: null,
+ },
+ };
+
+ mockUseRuleFromTimeline.mockImplementation((handleSetRuleFromTimeline) => {
+ useEffect(() => {
+ handleSetRuleFromTimeline(kqlQuery);
+ }, [handleSetRuleFromTimeline]);
+
+ return { onOpenTimeline, loading: false };
+ });
+
+ render( , {
+ wrapper: TestProviders,
+ });
+
+ expect(screen.getAllByTestId('query-bar')[0].textContent).toBe(
+ `${kqlQuery.queryBar.query.query} ${kqlQuery.queryBar.query.language}`
+ );
});
- setFormRef(defineStepForm);
+ it('updates EQL query correctly', async () => {
+ const eqlQuery = {
+ index: ['.alerts-security.alerts-default', 'logs-*', 'packetbeat-*'],
+ queryBar: {
+ filters: [],
+ query: {
+ query: 'process where true',
+ language: 'eql',
+ },
+ saved_id: null,
+ },
+ eqlOptions: {
+ eventCategoryField: 'cool.field',
+ tiebreakerField: 'another.field',
+ timestampField: 'cool.@timestamp',
+ query: 'process where true',
+ size: 77,
+ },
+ };
+
+ mockUseRuleFromTimeline.mockImplementation((handleSetRuleFromTimeline) => {
+ useEffect(() => {
+ handleSetRuleFromTimeline(eqlQuery);
+ }, [handleSetRuleFromTimeline]);
+
+ return { onOpenTimeline, loading: false };
+ });
- return (
+ render( , {
+ wrapper: TestProviders,
+ });
+
+ expect(screen.getByTestId(`eqlQueryBarTextInput`).textContent).toEqual(
+ eqlQuery.queryBar.query.query
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('eql-settings-trigger'));
+ });
+
+ expect(
+ within(screen.getByTestId('eql-event-category-field')).queryByRole('combobox')
+ ).toHaveValue(eqlQuery.eqlOptions.eventCategoryField);
+
+ expect(
+ within(screen.getByTestId('eql-tiebreaker-field')).queryByRole('combobox')
+ ).toHaveValue(eqlQuery.eqlOptions.tiebreakerField);
+
+ expect(within(screen.getByTestId('eql-timestamp-field')).queryByRole('combobox')).toHaveValue(
+ eqlQuery.eqlOptions.timestampField
+ );
+ });
+ });
+});
+
+interface TestFormProps {
+ ruleType?: RuleType;
+ initialState?: Partial;
+ onSubmit?: FormSubmitHandler;
+}
+
+function TestForm({
+ ruleType = stepDefineDefaultValue.ruleType,
+ initialState,
+ onSubmit,
+}: TestFormProps): JSX.Element {
+ const [selectedEqlOptions, setSelectedEqlOptions] = useState(stepDefineDefaultValue.eqlOptions);
+ const { form } = useForm({
+ options: { stripEmptyFields: false },
+ schema: defineRuleSchema,
+ defaultValue: { ...stepDefineDefaultValue, ...initialState },
+ onSubmit,
+ });
+
+ return (
+ <>
{}}
- setIsThreatQueryBarValid={() => {}}
+ setIsQueryBarValid={jest.fn()}
+ setIsThreatQueryBarValid={jest.fn()}
ruleType={ruleType}
index={stepDefineDefaultValue.index}
threatIndex={stepDefineDefaultValue.threatIndex}
@@ -321,87 +541,51 @@ describe('StepDefineRule', () => {
thresholdFields={[]}
enableThresholdSuppression={false}
/>
- );
- };
- beforeEach(() => {
- jest.clearAllMocks();
- mockUseRuleFromTimeline.mockReturnValue({ onOpenTimeline, loading: false });
+
+ {'Submit'}
+
+ >
+ );
+}
+
+function submitForm(): Promise {
+ return act(async () => {
+ fireEvent.click(screen.getByText('Submit'));
});
- it('renders correctly', () => {
- const wrapper = mount( {}} />, {
- wrappingComponent: TestProviders,
- });
+}
- expect(wrapper.find('Form[data-test-subj="stepDefineRule"]')).toHaveLength(1);
+function addRelatedIntegrationRow(): Promise {
+ return act(async () => {
+ fireEvent.click(screen.getByText('Add integration'));
});
+}
- const kqlQuery = {
- index: ['.alerts-security.alerts-default', 'logs-*', 'packetbeat-*'],
- queryBar: {
- filters: [],
- query: {
- query: 'host.name:*',
- language: 'kuery',
- },
- saved_id: null,
- },
- };
+function showEuiComboBoxOptions(comboBoxToggleButton: HTMLElement): Promise {
+ fireEvent.click(comboBoxToggleButton);
- const eqlQuery = {
- index: ['.alerts-security.alerts-default', 'logs-*', 'packetbeat-*'],
- queryBar: {
- filters: [],
- query: {
- query: 'process where true',
- language: 'eql',
- },
- saved_id: null,
- },
- eqlOptions: {
- eventCategoryField: 'cool.field',
- tiebreakerField: 'another.field',
- timestampField: 'cool.@timestamp',
- query: 'process where true',
- size: 77,
- },
- };
- it('handleSetRuleFromTimeline correctly updates the query', () => {
- mockUseRuleFromTimeline.mockImplementation((handleSetRuleFromTimeline) => {
- handleSetRuleFromTimeline(kqlQuery);
- return { onOpenTimeline, loading: false };
- });
- const { getAllByTestId } = render(
-
- {}} />
-
- );
- expect(getAllByTestId('query-bar')[0].textContent).toEqual(
- `${kqlQuery.queryBar.query.query} ${kqlQuery.queryBar.query.language}`
- );
+ return waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
});
- it('handleSetRuleFromTimeline correctly updates eql query', async () => {
- mockUseRuleFromTimeline
- .mockImplementationOnce(() => ({ onOpenTimeline, loading: false }))
- .mockImplementationOnce((handleSetRuleFromTimeline) => {
- handleSetRuleFromTimeline(eqlQuery);
- return { onOpenTimeline, loading: false };
- });
- const { getByTestId } = render(
-
- {}} ruleType="eql" />
-
- );
- expect(getByTestId(`eqlQueryBarTextInput`).textContent).toEqual(eqlQuery.queryBar.query.query);
- fireEvent.click(getByTestId(`eql-settings-trigger`));
-
- expect(within(getByTestId(`eql-event-category-field`)).queryByRole('combobox')).toHaveValue(
- eqlQuery.eqlOptions.eventCategoryField
- );
- expect(within(getByTestId(`eql-tiebreaker-field`)).queryByRole('combobox')).toHaveValue(
- eqlQuery.eqlOptions.tiebreakerField
- );
- expect(within(getByTestId(`eql-timestamp-field`)).queryByRole('combobox')).toHaveValue(
- eqlQuery.eqlOptions.timestampField
- );
+}
+
+function selectEuiComboBoxOption({
+ comboBoxToggleButton,
+ optionIndex,
+}: {
+ comboBoxToggleButton: HTMLElement;
+ optionIndex: number;
+}): Promise {
+ return act(async () => {
+ await showEuiComboBoxOptions(comboBoxToggleButton);
+
+ fireEvent.click(within(screen.getByRole('listbox')).getAllByRole('option')[optionIndex]);
});
-});
+}
+
+function setVersion({ input, value }: { input: HTMLInputElement; value: string }): Promise {
+ return act(async () => {
+ fireEvent.input(input, {
+ target: { value },
+ });
+ });
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx
index 987274ee5488ec..317deb94797380 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx
@@ -99,6 +99,7 @@ import { DurationInput } from '../duration_input';
import { MINIMUM_LICENSE_FOR_SUPPRESSION } from '../../../../../common/detection_engine/constants';
import { useUpsellingMessage } from '../../../../common/hooks/use_upselling';
import { useAlertSuppression } from '../../../rule_management/logic/use_alert_suppression';
+import { RelatedIntegrations } from '../../../rule_creation/components/related_integrations';
const CommonUseField = getUseField({ component: Field });
@@ -1114,6 +1115,9 @@ const StepDefineRuleComponent: FC = ({
>
+
+
+
= {
],
},
relatedIntegrations: {
- label: i18n.translate(
- 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsLabel',
- {
- defaultMessage: 'Related integrations',
- }
- ),
- helpText: i18n.translate(
- 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsHelpText',
- {
- defaultMessage: 'Integration related to this Rule.',
- }
- ),
+ type: FIELD_TYPES.JSON,
},
requiredFields: {
label: i18n.translate(
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts
index 40da9e9a204a41..e00e18ed7f6ea4 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts
@@ -129,11 +129,55 @@ describe('helpers', () => {
type: 'query',
timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2',
timeline_title: 'Titled timeline',
+ related_integrations: [
+ {
+ package: 'aws',
+ integration: 'route53',
+ version: '~1.2.3',
+ },
+ {
+ package: 'system',
+ version: '^1.2.3',
+ },
+ ],
};
expect(result).toEqual(expected);
});
+ test('filters out empty related integrations', () => {
+ const result = formatDefineStepData({
+ ...mockData,
+ relatedIntegrations: [
+ { package: '', version: '' },
+ {
+ package: 'aws',
+ integration: 'route53',
+ version: '~1.2.3',
+ },
+ { package: '', version: '' },
+ {
+ package: 'system',
+ version: '^1.2.3',
+ },
+ ],
+ });
+
+ expect(result).toMatchObject({
+ related_integrations: [
+ {
+ package: 'aws',
+ integration: 'route53',
+ version: '~1.2.3',
+ },
+ {
+ package: 'system',
+ version: '^1.2.3',
+ },
+ ],
+ });
+ });
+
describe('saved_query and query rule types', () => {
test('returns query rule if savedId provided but shouldLoadQueryDynamically != true', () => {
const mockStepData: DefineStepRule = {
@@ -308,6 +352,17 @@ describe('helpers', () => {
machine_learning_job_id: ['some_jobert_id'],
timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2',
timeline_title: 'Titled timeline',
+ related_integrations: [
+ {
+ package: 'aws',
+ integration: 'route53',
+ version: '~1.2.3',
+ },
+ {
+ package: 'system',
+ version: '^1.2.3',
+ },
+ ],
};
expect(result).toEqual(expected);
@@ -501,6 +556,17 @@ describe('helpers', () => {
threat_index: mockStepData.threatIndex,
index: mockStepData.index,
threat_filters: threatFilters,
+ related_integrations: [
+ {
+ package: 'aws',
+ integration: 'route53',
+ version: '~1.2.3',
+ },
+ {
+ package: 'system',
+ version: '^1.2.3',
+ },
+ ],
};
expect(result).toEqual(expected);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts
index 81be2839c00296..18f23824b77a78 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts
@@ -403,6 +403,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep
const baseFields = {
type: ruleType,
+ related_integrations: defineStepData.relatedIntegrations?.filter((ri) => !isEmpty(ri.package)),
...(timeline.id != null &&
timeline.title != null && {
timeline_id: timeline.id,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx
index ff96fd64f027f8..807dc4b04f1b3c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx
@@ -397,6 +397,7 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
const aboutStepFormValid = await aboutStepForm.validate();
const scheduleStepFormValid = await scheduleStepForm.validate();
const actionsStepFormValid = await actionsStepForm.validate();
+
if (
defineStepFormValid &&
aboutStepFormValid &&
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts
index 49bd1649c3471f..80b0d3eedc8b76 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts
@@ -216,7 +216,17 @@ export const mockDefineStepRule = (): DefineStepRule => ({
queryBar: mockQueryBar,
threatQueryBar: mockQueryBar,
requiredFields: [],
- relatedIntegrations: [],
+ relatedIntegrations: [
+ {
+ package: 'aws',
+ integration: 'route53',
+ version: '~1.2.3',
+ },
+ {
+ package: 'system',
+ version: '^1.2.3',
+ },
+ ],
threatMapping: [],
timeline: {
id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2',
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx
index 09caca91b30f3d..f0480cbef8f3be 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx
@@ -5,9 +5,6 @@
* 2.0.
*/
-import type { FC, PropsWithChildren } from 'react';
-import React from 'react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, cleanup } from '@testing-library/react-hooks';
import {
@@ -18,6 +15,7 @@ import {
import { useExecutionEvents } from './use_execution_events';
import { useToasts } from '../../../../common/lib/kibana';
import { api } from '../../api';
+import { createReactQueryWrapper } from '../../../../common/mock';
jest.mock('../../../../common/lib/kibana');
jest.mock('../../api');
@@ -33,21 +31,6 @@ describe('useExecutionEvents', () => {
cleanup();
});
- const createReactQueryWrapper = () => {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- // Turn retries off, otherwise we won't be able to test errors
- retry: false,
- },
- },
- });
- const wrapper: FC> = ({ children }) => (
- {children}
- );
- return wrapper;
- };
-
const render = () =>
renderHook(() => useExecutionEvents({ ruleId: SOME_RULE_ID }), {
wrapper: createReactQueryWrapper(),
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_results_table/use_execution_results.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_results_table/use_execution_results.test.tsx
index 45c7eaca3599ea..65d7ea7c3cda7c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_results_table/use_execution_results.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_results_table/use_execution_results.test.tsx
@@ -5,14 +5,12 @@
* 2.0.
*/
-import type { FC, PropsWithChildren } from 'react';
-import React from 'react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, cleanup } from '@testing-library/react-hooks';
import { useExecutionResults } from './use_execution_results';
import { useToasts } from '../../../../common/lib/kibana';
import { api } from '../../api';
+import { createReactQueryWrapper } from '../../../../common/mock';
jest.mock('../../../../common/lib/kibana');
jest.mock('../../api');
@@ -28,21 +26,6 @@ describe('useExecutionResults', () => {
cleanup();
});
- const createReactQueryWrapper = () => {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- // Turn retries off, otherwise we won't be able to test errors
- retry: false,
- },
- },
- });
- const wrapper: FC> = ({ children }) => (
- {children}
- );
- return wrapper;
- };
-
const render = () =>
renderHook(
() =>
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.test.ts
index 54f6f9e1d24f8a..30d656c3bf79c3 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.test.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.test.ts
@@ -77,15 +77,19 @@ describe('Integration Details', () => {
{
package_name: 'aws',
package_title: 'AWS',
- package_version: '1.3.0',
+ latest_package_version: '1.3.0',
+ installed_package_version: '1.3.0',
integration_name: 'route53',
integration_title: 'AWS Route 53',
+ is_installed: true,
is_enabled: false,
},
{
package_name: 'system',
package_title: 'System',
- package_version: '1.2.5',
+ latest_package_version: '1.2.5',
+ installed_package_version: '1.2.5',
+ is_installed: true,
is_enabled: true,
},
]
@@ -121,15 +125,19 @@ describe('Integration Details', () => {
{
package_name: 'aws',
package_title: 'AWS',
- package_version: '1.2.0',
+ latest_package_version: '1.2.0',
+ installed_package_version: '1.2.0',
integration_name: 'route53',
integration_title: 'AWS Route 53',
+ is_installed: true,
is_enabled: false,
},
{
package_name: 'system',
package_title: 'System',
- package_version: '1.2.2',
+ latest_package_version: '1.2.2',
+ installed_package_version: '1.2.2',
+ is_installed: true,
is_enabled: true,
},
]
@@ -156,15 +164,19 @@ describe('Integration Details', () => {
{
package_name: 'aws',
package_title: 'AWS',
- package_version: '2.0.1',
+ latest_package_version: '2.0.1',
+ installed_package_version: '2.0.1',
integration_name: 'route53',
integration_title: 'AWS Route 53',
+ is_installed: true,
is_enabled: false,
},
{
package_name: 'system',
package_title: 'System',
- package_version: '1.3.0',
+ latest_package_version: '1.3.0',
+ installed_package_version: '1.3.0',
+ is_installed: true,
is_enabled: true,
},
]
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts
index 2dfde8348f2f77..e8537931bae524 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts
@@ -8,10 +8,7 @@
import { capitalize } from 'lodash';
import semver from 'semver';
-import type {
- InstalledIntegration,
- InstalledIntegrationArray,
-} from '../../../../../common/api/detection_engine/fleet_integrations';
+import type { Integration } from '../../../../../common/api/detection_engine/fleet_integrations';
import type {
RelatedIntegration,
RelatedIntegrationArray,
@@ -42,57 +39,60 @@ export interface UnknownInstallationStatus {
}
/**
- * Given an array of integrations and an array of installed integrations this will return an
- * array of integrations augmented with install details like targetVersion, and `version_satisfied`
+ * Given an array of integrations and an array of all known integrations this will return an
+ * array of integrations augmented with details like targetVersion, and `version_satisfied`
* has.
*/
export const calculateIntegrationDetails = (
relatedIntegrations: RelatedIntegrationArray,
- installedIntegrations: InstalledIntegrationArray | undefined
+ knownIntegrations: Integration[] | undefined
): IntegrationDetails[] => {
- const integrationMatches = findIntegrationMatches(relatedIntegrations, installedIntegrations);
- const integrationDetails = integrationMatches.map((integration) => {
- return createIntegrationDetails(integration);
- });
+ const integrationMatches = findIntegrationMatches(relatedIntegrations, knownIntegrations);
+ const integrationDetails = integrationMatches.map((integration) =>
+ createIntegrationDetails(integration)
+ );
- return integrationDetails.sort((a, b) => {
- return a.integrationTitle.localeCompare(b.integrationTitle);
- });
+ return integrationDetails.sort((a, b) => a.integrationTitle.localeCompare(b.integrationTitle));
};
interface IntegrationMatch {
related: RelatedIntegration;
- installed: InstalledIntegration | null;
+ found?: Integration;
isLoaded: boolean;
}
const findIntegrationMatches = (
relatedIntegrations: RelatedIntegrationArray,
- installedIntegrations: InstalledIntegrationArray | undefined
+ integrations: Integration[] | undefined
): IntegrationMatch[] => {
+ const integrationsMap = new Map(
+ (integrations ?? []).map((integration) => [
+ `${integration.package_name}${integration.integration_name ?? ''}`,
+ integration,
+ ])
+ );
+
return relatedIntegrations.map((ri: RelatedIntegration) => {
- if (installedIntegrations == null) {
+ const key = `${ri.package}${ri.integration ?? ''}`;
+ const matchIntegration = integrationsMap.get(key);
+
+ if (!matchIntegration) {
return {
related: ri,
- installed: null,
isLoaded: false,
};
- } else {
- const match = installedIntegrations.find(
- (ii: InstalledIntegration) =>
- ii.package_name === ri.package && ii?.integration_name === ri?.integration
- );
- return {
- related: ri,
- installed: match ?? null,
- isLoaded: true,
- };
}
+
+ return {
+ related: ri,
+ found: matchIntegration,
+ isLoaded: true,
+ };
});
};
const createIntegrationDetails = (integration: IntegrationMatch): IntegrationDetails => {
- const { related, installed, isLoaded } = integration;
+ const { related, found, isLoaded } = integration;
const packageName = related.package;
const integrationName = related.integration ?? null;
@@ -117,8 +117,7 @@ const createIntegrationDetails = (integration: IntegrationMatch): IntegrationDet
};
}
- // We know that the integration is not installed
- if (installed == null) {
+ if (!found) {
const integrationTitle = getCapitalizedTitle(packageName, integrationName);
const targetVersion = getMinimumConcreteVersionMatchingSemver(requiredVersion);
const targetUrl = buildTargetUrl(packageName, integrationName, targetVersion);
@@ -140,35 +139,34 @@ const createIntegrationDetails = (integration: IntegrationMatch): IntegrationDet
};
}
- // We know that the integration is installed
- {
- const integrationTitle = installed.integration_title ?? installed.package_title;
-
- // Version check e.g. installed version `1.2.3` satisfies required version `~1.2.1`
- const installedVersion = installed.package_version;
- const isVersionSatisfied = semver.satisfies(installedVersion, requiredVersion);
- const targetVersion = isVersionSatisfied
+ const integrationTitle = found.integration_title ?? found.package_title;
+ // Version check e.g. installed version `1.2.3` satisfies required version `~1.2.1`
+ const installedVersion = found.installed_package_version ?? '';
+ const isVersionSatisfied = installedVersion
+ ? semver.satisfies(installedVersion, requiredVersion, { includePrerelease: true })
+ : true;
+ const targetVersion =
+ installedVersion && isVersionSatisfied
? installedVersion
: getMinimumConcreteVersionMatchingSemver(requiredVersion);
- const targetUrl = buildTargetUrl(packageName, integrationName, targetVersion);
-
- return {
- packageName,
- integrationName,
- integrationTitle,
- requiredVersion,
- targetVersion,
- targetUrl,
- installationStatus: {
- isKnown: true,
- isInstalled: true,
- isEnabled: installed.is_enabled,
- isVersionMismatch: !isVersionSatisfied,
- installedVersion,
- },
- };
- }
+ const targetUrl = buildTargetUrl(packageName, integrationName, targetVersion);
+
+ return {
+ packageName,
+ integrationName,
+ integrationTitle,
+ requiredVersion,
+ targetVersion,
+ targetUrl,
+ installationStatus: {
+ isKnown: true,
+ isInstalled: found.is_installed,
+ isEnabled: found.is_enabled,
+ isVersionMismatch: !isVersionSatisfied,
+ installedVersion,
+ },
+ };
};
const getCapitalizedTitle = (packageName: string, integrationName: string | null): string => {
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx
index 30463c744073e4..fecbb3e85df399 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx
@@ -31,26 +31,22 @@ const IntegrationStatusBadgeComponent: React.FC = (
const { isInstalled, isEnabled } = installationStatus;
- const badgeInstalledColor = 'success';
- const badgeUninstalledColor = '#E0E5EE';
- const badgeColor = isInstalled ? badgeInstalledColor : badgeUninstalledColor;
-
- const badgeTooltip = isInstalled
+ const color = isEnabled ? 'success' : isInstalled ? 'primary' : undefined;
+ const tooltipText = isInstalled
? isEnabled
? i18n.INTEGRATIONS_ENABLED_TOOLTIP
: i18n.INTEGRATIONS_INSTALLED_TOOLTIP
: i18n.INTEGRATIONS_UNINSTALLED_TOOLTIP;
-
- const badgeText = isInstalled
- ? isEnabled
- ? i18n.INTEGRATIONS_ENABLED
- : i18n.INTEGRATIONS_INSTALLED
+ const statusText = isEnabled
+ ? i18n.INTEGRATIONS_ENABLED
+ : isInstalled
+ ? i18n.INTEGRATIONS_DISABLED
: i18n.INTEGRATIONS_UNINSTALLED;
return (
-
-
- {badgeText}
+
+
+ {statusText}
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/translations.ts
index 1037993a246d26..dbd928315cf5c3 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/translations.ts
@@ -7,10 +7,10 @@
import { i18n } from '@kbn/i18n';
-export const INTEGRATIONS_INSTALLED = i18n.translate(
- 'xpack.securitySolution.detectionEngine.relatedIntegrations.installedTitle',
+export const INTEGRATIONS_DISABLED = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.relatedIntegrations.disabledTitle',
{
- defaultMessage: 'Installed',
+ defaultMessage: 'Disabled',
}
);
@@ -40,7 +40,7 @@ export const INTEGRATIONS_UNINSTALLED_TOOLTIP = i18n.translate(
export const INTEGRATIONS_ENABLED = i18n.translate(
'xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTitle',
{
- defaultMessage: 'Installed: enabled',
+ defaultMessage: 'Enabled',
}
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx
deleted file mode 100644
index 01b7d5fe6e6133..00000000000000
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { useQuery } from '@tanstack/react-query';
-
-import type { InstalledIntegrationArray } from '../../../../../common/api/detection_engine/fleet_integrations';
-import { fleetIntegrationsApi } from '../../../../detection_engine/fleet_integrations';
-// import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
-// import * as i18n from './translations';
-
-const ONE_MINUTE = 60000;
-
-export interface UseInstalledIntegrationsArgs {
- packages?: string[];
- skip?: boolean;
-}
-
-export const useInstalledIntegrations = ({
- packages,
- skip = false,
-}: UseInstalledIntegrationsArgs) => {
- // const { addError } = useAppToasts();
-
- return useQuery(
- [
- 'installedIntegrations',
- {
- packages,
- },
- ],
- async ({ signal }) => {
- const integrations = await fleetIntegrationsApi.fetchInstalledIntegrations({
- packages,
- signal,
- });
- return integrations.installed_integrations ?? [];
- },
- {
- keepPreviousData: true,
- staleTime: ONE_MINUTE * 5,
- enabled: !skip,
- onError: (e) => {
- // Suppressing for now to prevent excessive errors when fleet isn't configured
- // addError(e, { title: i18n.INTEGRATIONS_FETCH_FAILURE });
- },
- }
- );
-};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_integrations.test.tsx
similarity index 65%
rename from x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx
rename to x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_integrations.test.tsx
index 74791dfff20325..eba2248f82a4d1 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_integrations.test.tsx
@@ -5,20 +5,18 @@
* 2.0.
*/
-import type { FC, PropsWithChildren } from 'react';
-import React from 'react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, cleanup } from '@testing-library/react-hooks';
-import { useInstalledIntegrations } from './use_installed_integrations';
+import { useIntegrations } from './use_integrations';
import { fleetIntegrationsApi } from '../../../../detection_engine/fleet_integrations/api';
import { useToasts } from '../../../../common/lib/kibana';
+import { createReactQueryWrapper } from '../../../../common/mock';
jest.mock('../../../../detection_engine/fleet_integrations/api');
jest.mock('../../../../common/lib/kibana');
-describe('useInstalledIntegrations', () => {
+describe('useIntegrations', () => {
beforeEach(() => {
jest.clearAllMocks();
});
@@ -27,26 +25,10 @@ describe('useInstalledIntegrations', () => {
cleanup();
});
- const createReactQueryWrapper = () => {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- // Turn retries off, otherwise we won't be able to test errors
- retry: false,
- },
- },
- });
- const wrapper: FC> = ({ children }) => (
- {children}
- );
- return wrapper;
- };
-
const render = ({ skip } = { skip: false }) =>
renderHook(
() =>
- useInstalledIntegrations({
- packages: [],
+ useIntegrations({
skip,
}),
{
@@ -54,31 +36,22 @@ describe('useInstalledIntegrations', () => {
}
);
- it('calls the API via fetchInstalledIntegrations', async () => {
- const fetchInstalledIntegrations = jest.spyOn(
- fleetIntegrationsApi,
- 'fetchInstalledIntegrations'
- );
+ it('calls the API via fetchAllIntegrations', async () => {
+ const fetchAllIntegrations = jest.spyOn(fleetIntegrationsApi, 'fetchAllIntegrations');
const { waitForNextUpdate } = render();
await waitForNextUpdate();
- expect(fetchInstalledIntegrations).toHaveBeenCalledTimes(1);
- expect(fetchInstalledIntegrations).toHaveBeenLastCalledWith(
- expect.objectContaining({ packages: [] })
- );
+ expect(fetchAllIntegrations).toHaveBeenCalledTimes(1);
});
it('does not call the API when skip is true', async () => {
- const fetchInstalledIntegrations = jest.spyOn(
- fleetIntegrationsApi,
- 'fetchInstalledIntegrations'
- );
+ const fetchAllIntegrations = jest.spyOn(fleetIntegrationsApi, 'fetchAllIntegrations');
render({ skip: true });
- expect(fetchInstalledIntegrations).toHaveBeenCalledTimes(0);
+ expect(fetchAllIntegrations).toHaveBeenCalledTimes(0);
});
it('fetches data from the API', async () => {
@@ -97,19 +70,32 @@ describe('useInstalledIntegrations', () => {
expect(result.current.isSuccess).toEqual(true);
expect(result.current.isError).toEqual(false);
expect(result.current.data).toEqual([
+ {
+ package_name: 'o365',
+ package_title: 'Microsoft 365',
+ latest_package_version: '1.2.0',
+ installed_package_version: '1.0.0',
+ is_installed: false,
+ is_enabled: false,
+ },
{
integration_name: 'audit',
integration_title: 'Audit Logs',
- is_enabled: true,
+
package_name: 'atlassian_bitbucket',
package_title: 'Atlassian Bitbucket',
- package_version: '1.0.1',
+ latest_package_version: '1.0.1',
+ installed_package_version: '1.0.1',
+ is_installed: true,
+ is_enabled: true,
},
{
- is_enabled: true,
package_name: 'system',
package_title: 'System',
- package_version: '1.6.4',
+ latest_package_version: '1.6.4',
+ installed_package_version: '1.6.4',
+ is_installed: true,
+ is_enabled: true,
},
]);
});
@@ -117,7 +103,7 @@ describe('useInstalledIntegrations', () => {
// Skipping until we re-enable errors
it.skip('handles exceptions from the API', async () => {
const exception = new Error('Boom!');
- jest.spyOn(fleetIntegrationsApi, 'fetchInstalledIntegrations').mockRejectedValue(exception);
+ jest.spyOn(fleetIntegrationsApi, 'fetchAllIntegrations').mockRejectedValue(exception);
const { result, waitForNextUpdate } = render();
@@ -138,7 +124,7 @@ describe('useInstalledIntegrations', () => {
// And shows a toast with the caught exception
expect(useToasts().addError).toHaveBeenCalledTimes(1);
expect(useToasts().addError).toHaveBeenCalledWith(exception, {
- title: 'Failed to fetch installed integrations',
+ title: 'Failed to fetch integrations',
});
});
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_integrations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_integrations.tsx
new file mode 100644
index 00000000000000..3a03fcb39fce6a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_integrations.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useQuery } from '@tanstack/react-query';
+
+import type { Integration } from '../../../../../common/api/detection_engine/fleet_integrations';
+import { fleetIntegrationsApi } from '../../../../detection_engine/fleet_integrations';
+
+const ONE_MINUTE = 60000;
+
+export interface UseIntegrationsArgs {
+ skip?: boolean;
+}
+
+export const useIntegrations = ({ skip = false }: UseIntegrationsArgs = {}) => {
+ return useQuery(
+ ['integrations'],
+ async ({ signal }) => {
+ const response = await fleetIntegrationsApi.fetchAllIntegrations({
+ signal,
+ });
+
+ return response.integrations ?? [];
+ },
+ {
+ keepPreviousData: true,
+ staleTime: ONE_MINUTE * 5,
+ enabled: !skip,
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts
index dc16d365fff939..92ba42873e1069 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts
@@ -10,7 +10,7 @@ import { useMemo } from 'react';
import type { RelatedIntegrationArray } from '../../../../../common/api/detection_engine/model/rule_schema';
import type { IntegrationDetails } from './integration_details';
import { calculateIntegrationDetails } from './integration_details';
-import { useInstalledIntegrations } from './use_installed_integrations';
+import { useIntegrations } from './use_integrations';
export interface UseRelatedIntegrationsResult {
integrations: IntegrationDetails[];
@@ -20,17 +20,14 @@ export interface UseRelatedIntegrationsResult {
export const useRelatedIntegrations = (
relatedIntegrations: RelatedIntegrationArray
): UseRelatedIntegrationsResult => {
- const { data: installedIntegrations } = useInstalledIntegrations({ packages: [] });
+ const { data: integrations } = useIntegrations();
return useMemo(() => {
- const integrationDetails = calculateIntegrationDetails(
- relatedIntegrations,
- installedIntegrations
- );
+ const integrationDetails = calculateIntegrationDetails(relatedIntegrations, integrations);
return {
integrations: integrationDetails,
- isLoaded: installedIntegrations != null,
+ isLoaded: integrations != null,
};
- }, [relatedIntegrations, installedIntegrations]);
+ }, [relatedIntegrations, integrations]);
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts
index fa0168c7d2e982..fa2ae6af7876e8 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts
@@ -37,6 +37,7 @@ import type {
RuleAction,
AlertSuppression,
ThresholdAlertSuppression,
+ RelatedIntegration,
} from '../../../../../common/api/detection_engine/model/rule_schema';
import type { SortOrder } from '../../../../../common/api/detection_engine';
import type { EqlOptionsSelected } from '../../../../../common/search_strategy';
@@ -144,7 +145,7 @@ export interface DefineStepRule {
queryBar: FieldValueQueryBar;
dataViewId?: string;
dataViewTitle?: string;
- relatedIntegrations: RelatedIntegrationArray;
+ relatedIntegrations?: RelatedIntegrationArray;
requiredFields: RequiredFieldArray;
ruleType: Type;
timeline: FieldValueTimeline;
@@ -223,6 +224,7 @@ export interface DefineStepRuleJson {
event_category_override?: string;
tiebreaker_field?: string;
alert_suppression?: AlertSuppression | ThresholdAlertSuppression;
+ related_integrations?: RelatedIntegration[];
}
export interface AboutStepRuleJson {
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts
similarity index 79%
rename from x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.tsx
rename to x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts
index b0c2e1c3a2ef51..c4910f5daa42a1 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts
@@ -5,12 +5,11 @@
* 2.0.
*/
-import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useKibana } from '../../../../common/lib/kibana';
import { createFindAlerts } from '../services/find_alerts';
import { useFetchAlerts, type UseAlertsQueryParams } from './use_fetch_alerts';
+import { createReactQueryWrapper } from '../../../../common/mock';
jest.mock('../../../../common/lib/kibana');
jest.mock('../services/find_alerts');
@@ -29,11 +28,6 @@ describe('useFetchAlerts', () => {
});
it('fetches alerts and handles loading state', async () => {
- const queryClient = new QueryClient();
- const wrapper = ({ children }: { children: React.ReactChild }) => (
- {children}
- );
-
jest
.mocked(createFindAlerts)
.mockReturnValue(
@@ -47,7 +41,9 @@ describe('useFetchAlerts', () => {
sort: [{ '@timestamp': 'desc' }],
};
- const { result, waitFor } = renderHook(() => useFetchAlerts(params), { wrapper });
+ const { result, waitFor } = renderHook(() => useFetchAlerts(params), {
+ wrapper: createReactQueryWrapper(),
+ });
expect(result.current.loading).toBe(true);
@@ -60,11 +56,6 @@ describe('useFetchAlerts', () => {
});
it('handles error state', async () => {
- const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
- const wrapper = ({ children }: { children: React.ReactChild }) => (
- {children}
- );
-
// hide console error due to the line after
jest.spyOn(console, 'error').mockImplementation(() => {});
@@ -79,7 +70,9 @@ describe('useFetchAlerts', () => {
sort: [{ '@timestamp': 'desc' }],
};
- const { result, waitFor } = renderHook(() => useFetchAlerts(params), { wrapper });
+ const { result, waitFor } = renderHook(() => useFetchAlerts(params), {
+ wrapper: createReactQueryWrapper(),
+ });
expect(result.current.loading).toBe(true);
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts
similarity index 66%
rename from x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.tsx
rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts
index c9d63d4432d6f8..6ebdc2bc4b7c74 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts
@@ -6,26 +6,27 @@
*/
import { renderHook } from '@testing-library/react-hooks';
-
+import { createReactQueryWrapper } from '../../../../common/mock';
import { useFetchRelatedCases } from './use_fetch_related_cases';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import type { ReactNode } from 'react';
-import React from 'react';
-const eventId = 'eventId';
+jest.mock('../../../../common/lib/kibana', () => ({
+ useKibana: jest.fn().mockReturnValue({
+ services: {
+ cases: {
+ api: {
+ getRelatedCases: jest.fn().mockResolvedValue([]),
+ },
+ },
+ },
+ }),
+}));
-const createWrapper = () => {
- const queryClient = new QueryClient();
- // eslint-disable-next-line react/display-name
- return ({ children }: { children: ReactNode }) => (
- {children}
- );
-};
+const eventId = 'eventId';
describe('useFetchRelatedCases', () => {
it(`should return loading true while data is loading`, () => {
const hookResult = renderHook(() => useFetchRelatedCases({ eventId }), {
- wrapper: createWrapper(),
+ wrapper: createReactQueryWrapper(),
});
expect(hookResult.result.current.loading).toEqual(true);
diff --git a/x-pack/plugins/security_solution/public/shared_imports.ts b/x-pack/plugins/security_solution/public/shared_imports.ts
index 8686aecb3b99f1..80d345d7102d92 100644
--- a/x-pack/plugins/security_solution/public/shared_imports.ts
+++ b/x-pack/plugins/security_solution/public/shared_imports.ts
@@ -11,9 +11,12 @@ export type {
FormData,
FormHook,
FormSchema,
+ FormSubmitHandler,
ValidationError,
+ ValidationFuncArg,
ValidationFunc,
ArrayItem,
+ FieldConfig,
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
export {
getUseField,
@@ -23,6 +26,7 @@ export {
FormDataProvider,
UseField,
UseMultiFields,
+ UseArray,
useForm,
useFormContext,
useFormData,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx
index c5ab49e8f4f72a..9d9de2c9004091 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx
@@ -9,12 +9,11 @@ import { cloneDeep } from 'lodash/fp';
import moment from 'moment';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { fireEvent, screen, render, waitFor } from '@testing-library/react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import '../../../../common/mock/formatted_relative';
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
import { mockTimelineResults } from '../../../../common/mock/timeline_results';
-import { TestProviders } from '../../../../common/mock';
+import { createReactQueryWrapper, TestProviders } from '../../../../common/mock';
import type { OpenTimelineResult, TimelineResultNote } from '../types';
import { NotePreviews } from '.';
import { useDeleteNote } from './hooks/use_delete_note';
@@ -39,7 +38,6 @@ describe('NotePreviews', () => {
let note1updated: number;
let note2updated: number;
let note3updated: number;
- let queryClient: QueryClient;
beforeEach(() => {
mockResults = cloneDeep(mockTimelineResults);
@@ -47,14 +45,6 @@ describe('NotePreviews', () => {
note2updated = moment(note1updated).add(1, 'minute').valueOf();
note3updated = moment(note2updated).add(1, 'minute').valueOf();
(useDeepEqualSelector as jest.Mock).mockReset();
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
-
(useDeleteNote as jest.Mock).mockReturnValue({
mutate: deleteMutateMock,
onSuccess: jest.fn(),
@@ -66,11 +56,9 @@ describe('NotePreviews', () => {
test('it renders a note preview for each note when isModal is false', () => {
const hasNotes: OpenTimelineResult[] = [{ ...mockResults[0] }];
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
hasNotes[0].notes?.forEach(({ savedObjectId }) => {
expect(wrapper.find(`[data-test-subj="note-preview-${savedObjectId}"]`).exists()).toBe(true);
@@ -80,11 +68,9 @@ describe('NotePreviews', () => {
test('it renders a note preview for each note when isModal is true', () => {
const hasNotes: OpenTimelineResult[] = [{ ...mockResults[0] }];
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
hasNotes[0].notes?.forEach(({ savedObjectId }) => {
expect(wrapper.find(`[data-test-subj="note-preview-${savedObjectId}"]`).exists()).toBe(true);
@@ -113,11 +99,9 @@ describe('NotePreviews', () => {
},
];
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('div.euiCommentEvent__headerUsername').at(1).text()).toEqual('bob');
});
@@ -144,11 +128,9 @@ describe('NotePreviews', () => {
},
];
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('div.euiCommentEvent__headerUsername').at(2).text()).toEqual('bob');
});
@@ -174,11 +156,9 @@ describe('NotePreviews', () => {
},
];
- const wrapper = mountWithIntl(
-
- {' '}
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('div.euiCommentEvent__headerUsername').at(2).text()).toEqual('bob');
});
@@ -188,9 +168,10 @@ describe('NotePreviews', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue(timeline);
const wrapper = mountWithIntl(
-
-
-
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="note-preview-description"]').first().text()).toContain(
@@ -202,11 +183,9 @@ describe('NotePreviews', () => {
const timeline = mockTimelineResults[0];
(useDeepEqualSelector as jest.Mock).mockReturnValue({ ...timeline, description: undefined });
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="note-preview-description"]').exists()).toBe(false);
});
@@ -216,19 +195,20 @@ describe('NotePreviews', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue(timeline);
const wrapper = mountWithIntl(
-
-
-
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="delete-note"] button').prop('disabled')).toBeTruthy();
@@ -239,20 +219,21 @@ describe('NotePreviews', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue(timeline);
const wrapper = mountWithIntl(
-
-
-
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="delete-note"] button').prop('disabled')).toBeFalsy();
@@ -268,30 +249,31 @@ describe('NotePreviews', () => {
render(
-
-
-
-
+
+ ,
+ {
+ wrapper: createReactQueryWrapper(),
+ }
);
fireEvent.click(screen.queryAllByTestId('delete-note')[0]);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx
index b6ed788faae0c9..a46fd3e70616fd 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx
@@ -9,7 +9,6 @@ import type { EuiButtonIconProps } from '@elastic/eui';
import { cloneDeep, omit } from 'lodash/fp';
import React from 'react';
import { ThemeProvider } from 'styled-components';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import '../../../../common/mock/formatted_relative';
@@ -24,6 +23,7 @@ import { TimelinesTable } from '.';
import * as i18n from '../translations';
import { getMockTimelinesTableProps } from './mocks';
import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock';
+import { createReactQueryWrapper } from '../../../../common/mock';
const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } });
@@ -40,17 +40,9 @@ jest.mock('react-redux', () => {
describe('#getCommonColumns', () => {
let mockResults: OpenTimelineResult[];
- let queryClient: QueryClient;
beforeEach(() => {
mockResults = cloneDeep(mockTimelineResults);
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
});
describe('Expand column', () => {
@@ -60,11 +52,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(hasNotes),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="expand-notes"]').exists()).toBe(true);
});
@@ -75,11 +65,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(missingNotes),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="expand-notes"]').exists()).toBe(false);
});
@@ -89,11 +77,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(nullNotes),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="expand-notes"]').exists()).toBe(false);
});
@@ -103,11 +89,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(emptylNotes),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="expand-notes"]').exists()).toBe(false);
});
@@ -118,11 +102,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(missingSavedObjectId),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="expand-notes"]').exists()).toBe(false);
});
@@ -132,11 +114,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(nullSavedObjectId),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
expect(wrapper.find('[data-test-subj="expand-notes"]').exists()).toBe(false);
});
@@ -146,11 +126,9 @@ describe('#getCommonColumns', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(hasNotes),
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
const props = wrapper
.find('[data-test-subj="expand-notes"]')
.first()
@@ -170,11 +148,9 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(hasNotes),
itemIdToExpandedNotesRowMap,
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
const props = wrapper
.find('[data-test-subj="expand-notes"]')
.first()
@@ -197,11 +173,9 @@ describe('#getCommonColumns', () => {
itemIdToExpandedNotesRowMap,
onToggleShowNotes,
};
- const wrapper = mountWithIntl(
-
-
-
- );
+ const wrapper = mountWithIntl( , {
+ wrappingComponent: createReactQueryWrapper(),
+ });
wrapper.find('[data-test-subj="expand-notes"]').first().simulate('click');
expect(onToggleShowNotes).toBeCalledWith({
@@ -229,11 +203,12 @@ describe('#getCommonColumns', () => {
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
wrapper.find('[data-test-subj="expand-notes"]').first().simulate('click');
@@ -250,11 +225,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(mockResults),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('thead tr th').at(1).text()).toContain(i18n.TIMELINE_NAME);
@@ -265,11 +241,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(mockResults),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -289,11 +266,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(missingSavedObjectId),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -311,11 +289,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(missingTitle),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -335,11 +314,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(withMissingSavedObjectIdAndTitle),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -356,11 +336,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(withJustWhitespaceTitle),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -380,11 +361,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(withMissingSavedObjectId),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -397,11 +379,12 @@ describe('#getCommonColumns', () => {
test('it renders a hyperlink when the timeline has a saved object id', () => {
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -421,11 +404,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(missingSavedObjectId),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(
@@ -444,11 +428,12 @@ describe('#getCommonColumns', () => {
onOpenTimeline,
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
wrapper
@@ -466,11 +451,12 @@ describe('#getCommonColumns', () => {
describe('Description column', () => {
test('it renders the expected column name', () => {
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('thead tr th').at(2).text()).toContain(i18n.DESCRIPTION);
@@ -478,11 +464,12 @@ describe('#getCommonColumns', () => {
test('it renders the description when the timeline has a description', () => {
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="description"]').first().text()).toEqual(
@@ -494,11 +481,12 @@ describe('#getCommonColumns', () => {
const missingDescription: OpenTimelineResult[] = [omit('description', { ...mockResults[0] })];
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="description"]').first().text()).toEqual(
getEmptyValue()
@@ -514,11 +502,12 @@ describe('#getCommonColumns', () => {
...getMockTimelinesTableProps(justWhitespaceDescription),
};
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="description"]').first().text()).toEqual(
getEmptyValue()
@@ -529,11 +518,12 @@ describe('#getCommonColumns', () => {
describe('Last Modified column', () => {
test('it renders the expected column name', () => {
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('thead tr th').at(3).text()).toContain(i18n.LAST_MODIFIED);
@@ -541,11 +531,12 @@ describe('#getCommonColumns', () => {
test('it renders the last modified (updated) date when the timeline has an updated property', () => {
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="updated"]').first().text().length).toBeGreaterThan(
@@ -558,11 +549,12 @@ describe('#getCommonColumns', () => {
const missingUpdated: OpenTimelineResult[] = [omit('updated', { ...mockResults[0] })];
const wrapper = mountWithIntl(
-
-
-
-
-
+
+
+ ,
+ {
+ wrappingComponent: createReactQueryWrapper(),
+ }
);
expect(wrapper.find('[data-test-subj="updated"]').first().text()).toEqual(getEmptyValue());
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.test.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.test.ts
index 5a26600948267e..94b01fbc0d57ac 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.test.ts
@@ -6,31 +6,30 @@
*/
import { renderHook } from '@testing-library/react-hooks';
-import type { InstalledIntegration } from '../../../../../../common/api/detection_engine/fleet_integrations';
+import type { Integration } from '../../../../../../common/api/detection_engine/fleet_integrations';
import { TestProviders } from '../../../../../common/mock';
import { ENTRA_ID_PACKAGE_NAME } from '../constants';
import { useManagedUser } from './use_managed_user';
-const makeInstalledIntegration = (
- pkgName = 'testPkg',
- isEnabled = false
-): InstalledIntegration => ({
+const makeIntegration = (pkgName = 'testPkg', isEnabled = false): Integration => ({
package_name: pkgName,
package_title: '',
- package_version: '',
+ latest_package_version: '',
+ installed_package_version: '',
integration_name: '',
integration_title: '',
+ is_installed: true,
is_enabled: isEnabled,
});
-const mockUseInstalledIntegrations = jest.fn().mockReturnValue({
+const mockUseIntegrations = jest.fn().mockReturnValue({
data: [],
});
jest.mock(
- '../../../../../detections/components/rules/related_integrations/use_installed_integrations',
+ '../../../../../detections/components/rules/related_integrations/use_integrations',
() => ({
- useInstalledIntegrations: () => mockUseInstalledIntegrations(),
+ useIntegrations: () => mockUseIntegrations(),
})
);
@@ -67,8 +66,8 @@ describe('useManagedUser', () => {
mockSearch.mockClear();
});
it('returns isIntegrationEnabled:true when it finds an enabled integration with the given name', () => {
- mockUseInstalledIntegrations.mockReturnValue({
- data: [makeInstalledIntegration(ENTRA_ID_PACKAGE_NAME, true)],
+ mockUseIntegrations.mockReturnValue({
+ data: [makeIntegration(ENTRA_ID_PACKAGE_NAME, true)],
});
const { result } = renderHook(() => useManagedUser('test-userName', undefined, false), {
@@ -79,8 +78,8 @@ describe('useManagedUser', () => {
});
it('returns isIntegrationEnabled:false when it does not find an enabled integration with the given name', () => {
- mockUseInstalledIntegrations.mockReturnValue({
- data: [makeInstalledIntegration('fake-name', true)],
+ mockUseIntegrations.mockReturnValue({
+ data: [makeIntegration('fake-name', true)],
});
const { result } = renderHook(() => useManagedUser('test-userName', undefined, false), {
@@ -130,7 +129,7 @@ describe('useManagedUser', () => {
it('should return loading false when the feature is disabled', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
- mockUseInstalledIntegrations.mockReturnValue({
+ mockUseIntegrations.mockReturnValue({
data: [],
isLoading: true,
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.ts
index 2b985291638952..46191c8a8fb35e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks/use_managed_user.ts
@@ -9,7 +9,7 @@ import { useEffect, useMemo } from 'react';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import type { ManagedUserHits } from '../../../../../../common/search_strategy/security_solution/users/managed_details';
-import { useInstalledIntegrations } from '../../../../../detections/components/rules/related_integrations/use_installed_integrations';
+import { useIntegrations } from '../../../../../detections/components/rules/related_integrations/use_integrations';
import { UsersQueries } from '../../../../../../common/search_strategy';
import { useSpaceId } from '../../../../../common/hooks/use_space_id';
import { useSearchStrategy } from '../../../../../common/containers/use_search_strategy';
@@ -69,8 +69,7 @@ export const useManagedUser = (
}
}, [from, search, to, isInitializing, defaultIndex, userName, isLoading, email, skip]);
- const { data: installedIntegrations, isLoading: loadingIntegrations } = useInstalledIntegrations({
- packages,
+ const { data: integrations, isLoading: loadingIntegrations } = useIntegrations({
skip,
});
@@ -85,11 +84,11 @@ export const useManagedUser = (
const isIntegrationEnabled = useMemo(
() =>
- !!installedIntegrations?.some(
+ !!integrations?.some(
({ package_name: packageName, is_enabled: isEnabled }) =>
isEnabled && packages.includes(packageName)
),
- [installedIntegrations]
+ [integrations]
);
return useMemo(
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/extract_integrations.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/extract_integrations.test.ts
new file mode 100644
index 00000000000000..e5837c8184a5f0
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/extract_integrations.test.ts
@@ -0,0 +1,715 @@
+/*
+ * 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 { PackageList, PackagePolicy, PackagePolicyInput } from '@kbn/fleet-plugin/common';
+import { extractIntegrations } from './extract_integrations';
+
+describe('extractIntegrations', () => {
+ describe('for packages with multiple policy templates', () => {
+ it('extracts package title', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ package_name: 'package-a',
+ integration_name: 'integration-a',
+ package_title: 'Package A',
+ }),
+ expect.objectContaining({
+ package_name: 'package-a',
+ integration_name: 'integration-b',
+ package_title: 'Package A',
+ }),
+ ]);
+ });
+
+ it('extracts integration title by concatenating package and capitalized integration titles', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ integration_title: 'Package A Integration a',
+ }),
+ expect.objectContaining({
+ integration_name: 'integration-b',
+ integration_title: 'Package A Integration b',
+ }),
+ ]);
+ });
+
+ it('extracts latest available version', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ latest_package_version: '1.1.1',
+ }),
+ expect.objectContaining({
+ integration_name: 'integration-b',
+ latest_package_version: '1.1.1',
+ }),
+ ]);
+ });
+
+ it('extracts not installed integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ is_installed: false,
+ is_enabled: false,
+ }),
+ expect.objectContaining({
+ integration_name: 'integration-b',
+ is_installed: false,
+ is_enabled: false,
+ }),
+ ]);
+ });
+
+ it('extracts installed integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ is_installed: true,
+ is_enabled: false,
+ }),
+ expect.objectContaining({
+ integration_name: 'integration-b',
+ is_installed: true,
+ is_enabled: false,
+ }),
+ ]);
+ });
+
+ it('extracts enabled integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+ const policies = [
+ {
+ inputs: [
+ {
+ enabled: true,
+ policy_template: 'integration-a',
+ },
+ {
+ enabled: true,
+ type: 'integration-b',
+ },
+ ],
+ package: {
+ name: 'package-a',
+ },
+ },
+ ] as PackagePolicy[];
+
+ const result = extractIntegrations(packages, policies);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ is_installed: true,
+ is_enabled: true,
+ }),
+ expect.objectContaining({
+ integration_name: 'integration-b',
+ is_installed: true,
+ is_enabled: true,
+ }),
+ ]);
+ });
+
+ it('extracts installed package version', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ {
+ name: 'integration-b',
+ title: 'Integration B',
+ },
+ ],
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+ const policies = [
+ {
+ inputs: [
+ {
+ enabled: true,
+ policy_template: 'integration-a',
+ },
+ {
+ enabled: true,
+ type: 'integration-b',
+ },
+ ],
+ package: {
+ name: 'package-a',
+ },
+ },
+ ] as PackagePolicy[];
+
+ const result = extractIntegrations(packages, policies);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ installed_package_version: '1.0.0',
+ }),
+ expect.objectContaining({
+ integration_name: 'integration-b',
+ installed_package_version: '1.0.0',
+ }),
+ ]);
+ });
+ });
+
+ describe('for packages with only one policy template', () => {
+ it('extracts package title', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ package_name: 'package-a',
+ package_title: 'Package A',
+ }),
+ ]);
+ });
+
+ it('extracts integration title by concatenating package and capitalized integration titles', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ integration_name: 'integration-a',
+ integration_title: 'Package A Integration a',
+ }),
+ ]);
+ });
+
+ it('omits integration_name and integration_title are omitted when package and integration names match', () => {
+ const packages = [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.not.objectContaining({
+ integration_name: expect.anything(),
+ integration_title: expect.anything(),
+ }),
+ ]);
+ });
+
+ it('extracts latest available version', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ latest_package_version: '1.1.1',
+ }),
+ ]);
+ });
+
+ it('extracts not installed integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ is_installed: false,
+ is_enabled: false,
+ }),
+ ]);
+ });
+
+ it('extracts installed integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ is_installed: true,
+ is_enabled: false,
+ }),
+ ]);
+ });
+
+ it('extracts enabled integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+ const policies = [
+ {
+ inputs: [
+ {
+ enabled: true,
+ policy_template: 'integration-a',
+ },
+ ],
+ package: {
+ name: 'package-a',
+ },
+ },
+ ] as PackagePolicy[];
+
+ const result = extractIntegrations(packages, policies);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ is_installed: true,
+ is_enabled: true,
+ }),
+ ]);
+ });
+
+ it('extracts installed package version', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ policy_templates: [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ },
+ ],
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+ const policies = [
+ {
+ inputs: [
+ {
+ enabled: true,
+ policy_template: 'integration-a',
+ },
+ ],
+ package: {
+ name: 'package-a',
+ },
+ },
+ ] as PackagePolicy[];
+
+ const result = extractIntegrations(packages, policies);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ installed_package_version: '1.0.0',
+ }),
+ ]);
+ });
+ });
+
+ describe('for packages without policy templates', () => {
+ it('extracts package title', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ package_name: 'package-a',
+ package_title: 'Package A',
+ }),
+ ]);
+ });
+
+ it('omits integration_name and integration_title', () => {
+ const packages = [
+ {
+ name: 'integration-a',
+ title: 'Integration A',
+ version: '1.1.1',
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.not.objectContaining({
+ integration_name: expect.anything(),
+ integration_title: expect.anything(),
+ }),
+ ]);
+ });
+
+ it('extracts latest available version', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ latest_package_version: '1.1.1',
+ }),
+ ]);
+ });
+
+ it('extracts not installed integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ is_installed: false,
+ is_enabled: false,
+ }),
+ ]);
+ });
+
+ it('extracts installed integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+
+ const result = extractIntegrations(packages, []);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ is_installed: true,
+ is_enabled: false,
+ }),
+ ]);
+ });
+
+ it('extracts enabled integrations', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+ const policies = [
+ {
+ package: {
+ name: 'package-a',
+ },
+ inputs: [] as PackagePolicyInput[],
+ },
+ ] as PackagePolicy[];
+
+ const result = extractIntegrations(packages, policies);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ is_installed: true,
+ is_enabled: true,
+ }),
+ ]);
+ });
+
+ it('extracts installed package version', () => {
+ const packages = [
+ {
+ name: 'package-a',
+ title: 'Package A',
+ version: '1.1.1',
+ status: 'installed',
+ savedObject: {
+ attributes: {
+ install_version: '1.0.0',
+ },
+ },
+ },
+ ] as PackageList;
+ const policies = [
+ {
+ package: {
+ name: 'package-a',
+ },
+ inputs: [] as PackagePolicyInput[],
+ },
+ ] as PackagePolicy[];
+
+ const result = extractIntegrations(packages, policies);
+
+ expect(result).toEqual([
+ expect.objectContaining({
+ installed_package_version: '1.0.0',
+ }),
+ ]);
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/extract_integrations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/extract_integrations.ts
new file mode 100644
index 00000000000000..23cd03cedbe364
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/extract_integrations.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { capitalize } from 'lodash';
+import type { PackageList, PackagePolicy } from '@kbn/fleet-plugin/common';
+import type { Integration } from '../../../../../../common/api/detection_engine/fleet_integrations/model/integrations';
+
+export function extractIntegrations(
+ packages: PackageList,
+ packagePolicies: PackagePolicy[]
+): Integration[] {
+ const result: Integration[] = [];
+ const enabledIntegrationsSet = extractEnabledIntegrations(packagePolicies);
+
+ for (const fleetPackage of packages) {
+ const packageName = fleetPackage.name;
+ const packageTitle = fleetPackage.title;
+ const isPackageInstalled = fleetPackage.status === 'installed';
+ // Actual `installed_version` is buried in SO, root `version` is latest package version available
+ const installedPackageVersion = fleetPackage.savedObject?.attributes.install_version;
+ // Policy templates correspond to package's integrations.
+ const packagePolicyTemplates = fleetPackage.policy_templates ?? [];
+
+ for (const policyTemplate of packagePolicyTemplates) {
+ const integrationId = getIntegrationId(packageName, policyTemplate.name);
+ const integrationName = policyTemplate.name;
+ const integrationTitle =
+ packagePolicyTemplates.length === 1 && policyTemplate.name === fleetPackage.name
+ ? packageTitle
+ : `${packageTitle} ${capitalize(policyTemplate.title)}`;
+
+ const integration: Integration = {
+ package_name: packageName,
+ package_title: packageTitle,
+ latest_package_version: fleetPackage.version,
+ installed_package_version: installedPackageVersion,
+ integration_name: packageName !== integrationName ? integrationName : undefined,
+ integration_title: packageName !== integrationName ? integrationTitle : undefined,
+ is_installed: isPackageInstalled, // All integrations installed as a part of the package
+ is_enabled: enabledIntegrationsSet.has(integrationId),
+ };
+
+ result.push(integration);
+ }
+
+ // some packages don't have policy templates at al, e.g. Lateral Movement Detection
+ if (packagePolicyTemplates.length === 0) {
+ result.push({
+ package_name: packageName,
+ package_title: packageTitle,
+ latest_package_version: fleetPackage.version,
+ installed_package_version: installedPackageVersion,
+ is_installed: isPackageInstalled,
+ is_enabled: enabledIntegrationsSet.has(getIntegrationId(packageName, '')),
+ });
+ }
+ }
+
+ return result;
+}
+
+function extractEnabledIntegrations(packagePolicies: PackagePolicy[]): Set {
+ const enabledIntegrations = new Set();
+
+ for (const packagePolicy of packagePolicies) {
+ for (const input of packagePolicy.inputs) {
+ if (input.enabled) {
+ const packageName = packagePolicy.package?.name.trim() ?? ''; // e.g. 'cloudtrail'
+ const integrationName = (input.policy_template ?? input.type ?? '').trim(); // e.g. 'cloudtrail'
+ const enabledIntegrationKey = `${packageName}${integrationName}`;
+
+ enabledIntegrations.add(enabledIntegrationKey);
+ }
+ }
+
+ // Base package may not have policy template, so pull directly from `policy.package` if so
+ if (packagePolicy.package) {
+ const packageName = packagePolicy.package.name.trim();
+
+ enabledIntegrations.add(packageName);
+ }
+ }
+
+ return enabledIntegrations;
+}
+
+function getIntegrationId(packageName: string, integrationName: string): string {
+ return `${packageName}${integrationName}`;
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts
new file mode 100644
index 00000000000000..e5bae990528031
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../../common/detection_engine/constants';
+import { buildSiemResponse } from '../../../routes/utils';
+import type { SecuritySolutionPluginRouter } from '../../../../../types';
+import type { GetAllIntegrationsResponse } from '../../../../../../common/api/detection_engine/fleet_integrations';
+import { GET_ALL_INTEGRATIONS_URL } from '../../../../../../common/api/detection_engine/fleet_integrations';
+import { extractIntegrations } from './extract_integrations';
+import { sortPackagesBySecurityCategory } from './sort_packages_by_security_category';
+import { sortIntegrationsByStatus } from './sort_integrations_by_status';
+
+/**
+ * Returns an array of Fleet integrations and their packages
+ */
+export const getAllIntegrationsRoute = (router: SecuritySolutionPluginRouter) => {
+ router.versioned
+ .get({
+ access: 'internal',
+ path: GET_ALL_INTEGRATIONS_URL,
+ options: {
+ tags: ['access:securitySolution'],
+ },
+ })
+ .addVersion(
+ {
+ version: '1',
+ validate: false,
+ },
+ async (context, _, response) => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const ctx = await context.resolve(['core', 'securitySolution']);
+ const fleet = ctx.securitySolution.getInternalFleetServices();
+
+ const [packages, packagePolicies] = await Promise.all([
+ fleet.packages.getPackages(),
+ fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}),
+ ]);
+ // Elastic prebuilt rules is a special package and should be skipped
+ const packagesWithoutPrebuiltSecurityRules = packages.filter(
+ (x) => x.name !== PREBUILT_RULES_PACKAGE_NAME
+ );
+
+ sortPackagesBySecurityCategory(packagesWithoutPrebuiltSecurityRules);
+
+ const integrations = extractIntegrations(
+ packagesWithoutPrebuiltSecurityRules,
+ packagePolicies.items
+ );
+
+ sortIntegrationsByStatus(integrations);
+
+ const body: GetAllIntegrationsResponse = {
+ integrations,
+ };
+
+ return response.ok({ body });
+ } catch (err) {
+ const error = transformError(err);
+ return siemResponse.error({
+ body: error.message,
+ statusCode: error.statusCode,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/sort_integrations_by_status.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/sort_integrations_by_status.ts
new file mode 100644
index 00000000000000..fa626f369621b9
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/sort_integrations_by_status.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 { Integration } from '../../../../../../common/api/detection_engine/fleet_integrations/model/integrations';
+
+/**
+ * Sorts integrations in place
+ */
+export function sortIntegrationsByStatus(integration: Integration[]): void {
+ integration.sort((a, b) => {
+ if (a.is_enabled && !b.is_enabled) {
+ return -1;
+ } else if (!a.is_enabled && b.is_enabled) {
+ return 1;
+ }
+
+ if (a.is_installed && !b.is_installed) {
+ return -1;
+ } else if (!a.is_installed && b.is_installed) {
+ return 1;
+ }
+
+ return 0;
+ });
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/sort_packages_by_security_category.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/sort_packages_by_security_category.ts
new file mode 100644
index 00000000000000..25faae31dbd938
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/sort_packages_by_security_category.ts
@@ -0,0 +1,25 @@
+/*
+ * 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 { PackageList } from '@kbn/fleet-plugin/common';
+
+/**
+ * Sorts packages in place
+ */
+export function sortPackagesBySecurityCategory(packages: PackageList): void {
+ packages.sort((a, b) => {
+ if (a.categories?.includes('security') && !b.categories?.includes('security')) {
+ return -1;
+ }
+
+ if (!a.categories?.includes('security') && b.categories?.includes('security')) {
+ return 1;
+ }
+
+ return 0;
+ });
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts
index bf13e1f49134e4..407c7d54adc526 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import type { Logger } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildSiemResponse } from '../../../routes/utils';
import type { SecuritySolutionPluginRouter } from '../../../../../types';
@@ -17,10 +16,7 @@ import { createInstalledIntegrationSet } from './installed_integration_set';
/**
* Returns an array of installed Fleet integrations and their packages.
*/
-export const getInstalledIntegrationsRoute = (
- router: SecuritySolutionPluginRouter,
- logger: Logger
-) => {
+export const getInstalledIntegrationsRoute = (router: SecuritySolutionPluginRouter) => {
router.versioned
.get({
access: 'internal',
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.ts
index 2c6c2c2ae21f8a..cd60509ca5f51b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.ts
@@ -5,14 +5,11 @@
* 2.0.
*/
-import type { Logger } from '@kbn/core/server';
import type { SecuritySolutionPluginRouter } from '../../../../types';
-
+import { getAllIntegrationsRoute } from './get_all_integrations/route';
import { getInstalledIntegrationsRoute } from './get_installed_integrations/route';
-export const registerFleetIntegrationsRoutes = (
- router: SecuritySolutionPluginRouter,
- logger: Logger
-) => {
- getInstalledIntegrationsRoute(router, logger);
+export const registerFleetIntegrationsRoutes = (router: SecuritySolutionPluginRouter) => {
+ getAllIntegrationsRoute(router);
+ getInstalledIntegrationsRoute(router);
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts
index 07fb5640eb482a..48f097ac7a8609 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts
@@ -7,7 +7,6 @@
import * as z from 'zod';
import {
- RelatedIntegrationArray,
RequiredFieldArray,
SetupGuide,
RuleSignatureId,
@@ -35,7 +34,6 @@ export const PrebuiltRuleAsset = BaseCreateProps.and(TypeSpecificCreateProps).an
z.object({
rule_id: RuleSignatureId,
version: RuleVersion,
- related_integrations: RelatedIntegrationArray.optional(),
required_fields: RequiredFieldArray.optional(),
setup: SetupGuide.optional(),
})
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts
index a4fcd4797b75b1..d36f0ab4ad66e9 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts
@@ -57,7 +57,7 @@ export const updateRules = async ({
timelineTitle: ruleUpdate.timeline_title,
meta: ruleUpdate.meta,
maxSignals: ruleUpdate.max_signals ?? DEFAULT_MAX_SIGNALS,
- relatedIntegrations: existingRule.params.relatedIntegrations,
+ relatedIntegrations: ruleUpdate.related_integrations ?? [],
requiredFields: existingRule.params.requiredFields,
riskScore: ruleUpdate.risk_score,
riskScoreMapping: ruleUpdate.risk_score_mapping ?? [],
diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts
index 4365eb4bcc3fa3..bc1a26534cd6c7 100644
--- a/x-pack/plugins/security_solution/server/routes/index.ts
+++ b/x-pack/plugins/security_solution/server/routes/index.ts
@@ -78,7 +78,7 @@ export const initRoutes = (
previewRuleDataClient: IRuleDataClient,
previewTelemetryReceiver: ITelemetryReceiver
) => {
- registerFleetIntegrationsRoutes(router, logger);
+ registerFleetIntegrationsRoutes(router);
registerLegacyRuleActionsRoutes(router, logger);
registerPrebuiltRulesRoutes(router, security);
registerRuleExceptionsRoutes(router);
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 7e7d4218db8fe8..9d357b045bb5f0 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -34320,8 +34320,6 @@
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNameLabel": "Nom",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText": "Sélectionnez un champ pour vérifier les nouveaux termes.",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel": "URL de référence",
- "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsHelpText": "Intégration liée à cette règle.",
- "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsLabel": "Intégrations liées",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRequiredFieldsHelpText": "Champs requis pour le fonctionnement de cette règle.",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRequiredFieldsLabel": "Champ requis",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRuleNameOverrideHelpText": "Choisissez un champ de l'événement source pour remplir le nom de règle dans la liste d'alertes.",
@@ -35154,7 +35152,6 @@
"xpack.securitySolution.detectionEngine.relatedIntegrations.badgeTitle": "intégrations",
"xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTitle": "Installé : activé",
"xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTooltip": "L'intégration est installée et une politique d'intégration avec la configuration requise existe. Assurez-vous que des agents Elastic sont affectés à cette politique pour ingérer des événements compatibles.",
- "xpack.securitySolution.detectionEngine.relatedIntegrations.installedTitle": "Installé",
"xpack.securitySolution.detectionEngine.relatedIntegrations.installedTooltip": "L’intégration est installée. Configurez une politique d’intégration et assurez-vous que des agents Elastic sont affectés à cette politique pour ingérer des événements compatibles.",
"xpack.securitySolution.detectionEngine.relatedIntegrations.uninstalledTitle": "Non installé",
"xpack.securitySolution.detectionEngine.relatedIntegrations.uninstalledTooltip": "L’intégration n’est pas installée. Suivez le lien d'intégration pour installer et configurer l'intégration.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 60e15dd80a172f..9900efe948cae4 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -34289,8 +34289,6 @@
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNameLabel": "名前",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText": "新しい用語を確認するフィールドを選択します。",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel": "参照URL",
- "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsHelpText": "統合はこのルールに関連しています。",
- "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsLabel": "関連する統合",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRequiredFieldsHelpText": "このルールの機能に必要なフィールド。",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRequiredFieldsLabel": "必須フィールド",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRuleNameOverrideHelpText": "ソースイベントからフィールドを選択し、アラートリストのルール名を入力します。",
@@ -35123,7 +35121,6 @@
"xpack.securitySolution.detectionEngine.relatedIntegrations.badgeTitle": "統合",
"xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTitle": "インストール済み:有効",
"xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTooltip": "統合はインストールされ、必要な構成が行われている統合ポリシーが存在します。Elasticエージェントにこのポリシーが割り当てられていることを確認し、互換性があるイベントを取り込みます。",
- "xpack.securitySolution.detectionEngine.relatedIntegrations.installedTitle": "インストール済み",
"xpack.securitySolution.detectionEngine.relatedIntegrations.installedTooltip": "統合がインストールされています。統合ポリシーを構成し、Elasticエージェントにこのポリシーが割り当てられていることを確認して、対応するイベントを取り込みます。",
"xpack.securitySolution.detectionEngine.relatedIntegrations.uninstalledTitle": "未インストール",
"xpack.securitySolution.detectionEngine.relatedIntegrations.uninstalledTooltip": "統合はインストールされていません。統合リンクに従って、インストールし、統合を構成してください。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 430a3b8d469163..44d958e7b4527f 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -34332,8 +34332,6 @@
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNameLabel": "名称",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText": "选择字段以检查新字词。",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel": "引用 URL",
- "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsHelpText": "与此规则相关的集成。",
- "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRelatedIntegrationsLabel": "相关集成",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRequiredFieldsHelpText": "此规则正常运行所需的字段。",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRequiredFieldsLabel": "必填字段",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRuleNameOverrideHelpText": "从源事件中选择字段来填充告警列表中的规则名称。",
@@ -35166,7 +35164,6 @@
"xpack.securitySolution.detectionEngine.relatedIntegrations.badgeTitle": "集成",
"xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTitle": "已安装:已启用",
"xpack.securitySolution.detectionEngine.relatedIntegrations.enabledTooltip": "集成已安装,并且存在具有所需配置的集成策略。确保 Elastic 代理已分配此策略以采集兼容的事件。",
- "xpack.securitySolution.detectionEngine.relatedIntegrations.installedTitle": "已安装",
"xpack.securitySolution.detectionEngine.relatedIntegrations.installedTooltip": "已安装集成。配置集成策略,并确保 Elastic 代理已分配此策略以采集兼容的事件。",
"xpack.securitySolution.detectionEngine.relatedIntegrations.uninstalledTitle": "未安装",
"xpack.securitySolution.detectionEngine.relatedIntegrations.uninstalledTooltip": "未安装集成。访问集成链接以安装和配置集成。",
diff --git a/x-pack/test/api_integration/apis/ml/jobs/all_jobs_and_group_ids.ts b/x-pack/test/api_integration/apis/ml/jobs/all_jobs_and_group_ids.ts
new file mode 100644
index 00000000000000..b4efa23eedf2c6
--- /dev/null
+++ b/x-pack/test/api_integration/apis/ml/jobs/all_jobs_and_group_ids.ts
@@ -0,0 +1,80 @@
+/*
+ * 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';
+import { getCommonRequestHeader } from '../../../../functional/services/ml/common_api';
+import { USER } from '../../../../functional/services/ml/security_common';
+import { MULTI_METRIC_JOB_CONFIG, SINGLE_METRIC_JOB_CONFIG } from './common_jobs';
+
+export default ({ getService }: FtrProviderContext) => {
+ const esArchiver = getService('esArchiver');
+ const supertest = getService('supertestWithoutAuth');
+ const ml = getService('ml');
+
+ const testSetupJobConfigs = [SINGLE_METRIC_JOB_CONFIG, MULTI_METRIC_JOB_CONFIG];
+
+ const testCalendarsConfigs = [
+ {
+ calendar_id: `test_get_cal_1`,
+ job_ids: ['multi-metric'],
+ description: `Test calendar 1`,
+ },
+ {
+ calendar_id: `test_get_cal_2`,
+ job_ids: [MULTI_METRIC_JOB_CONFIG.job_id, 'multi-metric'],
+ description: `Test calendar 2`,
+ },
+ {
+ calendar_id: `test_get_cal_3`,
+ job_ids: ['brand-new-group'],
+ description: `Test calendar 3`,
+ },
+ ];
+
+ async function runRequest(user: USER, expectedResponseCode: number) {
+ const { body, status } = await supertest
+ .get('/internal/ml/jobs/all_jobs_and_group_ids')
+ .auth(user, ml.securityCommon.getPasswordForUser(user))
+ .set(getCommonRequestHeader('1'));
+ ml.api.assertResponseStatusCode(expectedResponseCode, status, body);
+
+ return body;
+ }
+
+ const expectedIds = {
+ jobIds: [MULTI_METRIC_JOB_CONFIG.job_id, SINGLE_METRIC_JOB_CONFIG.job_id],
+ groupIds: ['automated', 'brand-new-group', 'farequote', 'multi-metric', 'single-metric'],
+ };
+
+ describe('get all job and group IDs', function () {
+ before(async () => {
+ await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
+ await ml.testResources.setKibanaTimeZoneToUTC();
+
+ for (const job of testSetupJobConfigs) {
+ await ml.api.createAnomalyDetectionJob(job);
+ }
+ for (const cal of testCalendarsConfigs) {
+ await ml.api.createCalendar(cal.calendar_id, cal);
+ }
+ });
+
+ after(async () => {
+ await ml.api.cleanMlIndices();
+ });
+
+ it('returns expected list of job and group Ids', async () => {
+ const ids = await runRequest(USER.ML_VIEWER, 200);
+
+ expect(ids).to.eql(
+ expectedIds,
+ `response job and group IDs list should equal ${JSON.stringify(expectedIds)})`
+ );
+ });
+ });
+};
diff --git a/x-pack/test/api_integration/apis/ml/jobs/index.ts b/x-pack/test/api_integration/apis/ml/jobs/index.ts
index 561199b4e743da..96ed8131facd2a 100644
--- a/x-pack/test/api_integration/apis/ml/jobs/index.ts
+++ b/x-pack/test/api_integration/apis/ml/jobs/index.ts
@@ -28,5 +28,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./update_groups'));
loadTestFile(require.resolve('./category_results'));
loadTestFile(require.resolve('./jobs_with_time_range'));
+ loadTestFile(require.resolve('./all_jobs_and_group_ids'));
});
}
diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts
index a4f02524b98cf1..c22fb0325288b3 100644
--- a/x-pack/test/apm_api_integration/common/config.ts
+++ b/x-pack/test/apm_api_integration/common/config.ts
@@ -75,7 +75,7 @@ export interface CreateTest {
logSynthtraceEsClient: (
context: InheritedFtrProviderContext
) => Promise;
- synthtraceEsClient: (context: InheritedFtrProviderContext) => Promise;
+ apmSynthtraceEsClient: (context: InheritedFtrProviderContext) => Promise;
synthtraceKibanaClient: (
context: InheritedFtrProviderContext
) => Promise;
@@ -112,7 +112,7 @@ export function createTestConfig(
...services,
apmFtrConfig: () => config,
registry: RegistryProvider,
- synthtraceEsClient: (context: InheritedFtrProviderContext) => {
+ apmSynthtraceEsClient: (context: InheritedFtrProviderContext) => {
return bootstrapApmSynthtrace(context, synthtraceKibanaClient);
},
logSynthtraceEsClient: (context: InheritedFtrProviderContext) =>
diff --git a/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts b/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts
index 7fea0939d41ee9..95e71167aaab48 100644
--- a/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts
@@ -14,7 +14,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -103,7 +103,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
'service.language.name': 'javascript',
});
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -143,7 +143,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('labels.telemetry_auto_version takes precedence over agent.version for otelAgets', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts
index f98a82a6cd30ca..033d64e8f12e8e 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts
@@ -26,7 +26,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const es = getService('es');
const logger = getService('log');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
registry.when(
'fetching service anomalies with a trial license',
{ config: 'trial', archives: [] },
@@ -62,7 +62,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
];
});
- await synthtraceEsClient.index(events);
+ await apmSynthtraceEsClient.index(events);
await createAndRunApmMlJobs({ es, ml, environments: ['production'], logger });
});
@@ -72,7 +72,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
async function cleanup() {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
await cleanupRuleAndAlertState({ es, supertest, logger });
await ml.cleanMlIndices();
}
diff --git a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts
index 545706af43107e..46d62449de475f 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts
@@ -31,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const es = getService('es');
const logger = getService('log');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
registry.when('error count threshold alert', { config: 'basic', archives: [] }, () => {
const javaErrorMessage = 'a java error';
@@ -96,10 +96,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
];
});
- return Promise.all([synthtraceEsClient.index(events), synthtraceEsClient.index(phpEvents)]);
+ return Promise.all([
+ apmSynthtraceEsClient.index(events),
+ apmSynthtraceEsClient.index(phpEvents),
+ ]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
// FLAKY: https://github.com/elastic/kibana/issues/176948
describe('create rule without kql filter', () => {
diff --git a/x-pack/test/apm_api_integration/tests/alerts/generate_data.ts b/x-pack/test/apm_api_integration/tests/alerts/generate_data.ts
index 2765c0a1e265b2..79fd804aea1cd8 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/generate_data.ts
@@ -21,12 +21,12 @@ export const config = {
};
export async function generateLatencyData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -58,16 +58,16 @@ export async function generateLatencyData({
),
];
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
export async function generateErrorData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -110,5 +110,5 @@ export async function generateErrorData({
];
});
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
diff --git a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts
index c1b2243c1373d1..897f4467344442 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts
@@ -19,7 +19,7 @@ import { generateErrorData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -72,11 +72,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// FLAKY: https://github.com/elastic/kibana/issues/172769
describe('error_count', () => {
beforeEach(async () => {
- await generateErrorData({ serviceName: 'synth-go', start, end, synthtraceEsClient });
- await generateErrorData({ serviceName: 'synth-java', start, end, synthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient });
});
- afterEach(() => synthtraceEsClient.clean());
+ afterEach(() => apmSynthtraceEsClient.clean());
it('with data', async () => {
const options = getOptions();
@@ -308,11 +308,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// FLAKY: https://github.com/elastic/kibana/issues/176975
describe('error_count', () => {
before(async () => {
- await generateErrorData({ serviceName: 'synth-go', start, end, synthtraceEsClient });
- await generateErrorData({ serviceName: 'synth-java', start, end, synthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('with data', async () => {
const options = getOptionsWithFilterQuery();
diff --git a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts
index 2a3479bcffea07..bc11d1cb68fd93 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts
@@ -19,7 +19,7 @@ import { generateErrorData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -73,11 +73,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// FLAKY: https://github.com/elastic/kibana/issues/176977
describe('transaction_error_rate', () => {
before(async () => {
- await generateErrorData({ serviceName: 'synth-go', start, end, synthtraceEsClient });
- await generateErrorData({ serviceName: 'synth-java', start, end, synthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('with data', async () => {
const options = getOptions();
@@ -332,11 +332,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// FLAKY: https://github.com/elastic/kibana/issues/176983
describe('transaction_error_rate', () => {
before(async () => {
- await generateErrorData({ serviceName: 'synth-go', start, end, synthtraceEsClient });
- await generateErrorData({ serviceName: 'synth-java', start, end, synthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient });
+ await generateErrorData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('with data', async () => {
const options = getOptionsWithFilterQuery();
diff --git a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_transaction_duration.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_transaction_duration.spec.ts
index 3c149458ce2224..4bae66ed9e66b6 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_transaction_duration.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_transaction_duration.spec.ts
@@ -19,7 +19,7 @@ import { generateLatencyData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -76,11 +76,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// Failing: See https://github.com/elastic/kibana/issues/176989
describe('transaction_duration', () => {
before(async () => {
- await generateLatencyData({ serviceName: 'synth-go', start, end, synthtraceEsClient });
- await generateLatencyData({ serviceName: 'synth-java', start, end, synthtraceEsClient });
+ await generateLatencyData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient });
+ await generateLatencyData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('with data', async () => {
const options = getOptions();
@@ -304,11 +304,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when(`with data loaded and using KQL filter`, { config: 'basic', archives: [] }, () => {
describe('transaction_duration', () => {
before(async () => {
- await generateLatencyData({ serviceName: 'synth-go', start, end, synthtraceEsClient });
- await generateLatencyData({ serviceName: 'synth-java', start, end, synthtraceEsClient });
+ await generateLatencyData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient });
+ await generateLatencyData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('with data', async () => {
const options = getOptionsWithFilterQuery();
diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts
index 5eab7e772a4cda..7d4ec54fa52fbe 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts
@@ -31,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const es = getService('es');
const logger = getService('log');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const ruleParams = {
threshold: 3000,
@@ -68,11 +68,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.success(),
];
});
- return synthtraceEsClient.index(events);
+ return apmSynthtraceEsClient.index(events);
});
after(async () => {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
// FLAKY: https://github.com/elastic/kibana/issues/176996
diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts
index ec66b7aaf67ec9..6416337c3bfc9e 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts
@@ -30,7 +30,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const es = getService('es');
const logger = getService('log');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
registry.when('transaction error rate alert', { config: 'basic', archives: [] }, () => {
before(() => {
@@ -66,11 +66,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.success(),
];
});
- return synthtraceEsClient.index(events);
+ return apmSynthtraceEsClient.index(events);
});
after(async () => {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
// FLAKY: https://github.com/elastic/kibana/issues/177104
diff --git a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts
index 93effd3984daf2..1ba02adbade97f 100644
--- a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts
@@ -22,7 +22,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const ml = getService('ml');
const es = getService('es');
const logger = getService('log');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = moment().subtract(2, 'days');
const end = moment();
@@ -128,7 +128,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
];
});
- await synthtraceEsClient.index(events);
+ await apmSynthtraceEsClient.index(events);
});
afterEach(async () => {
@@ -136,7 +136,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
async function cleanup() {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
await ml.cleanMlIndices();
}
diff --git a/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts b/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts
deleted file mode 100644
index 5a98ec708bcf37..00000000000000
--- a/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts
+++ /dev/null
@@ -1,507 +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 moment from 'moment';
-import { log, apm, generateShortId, timerange } from '@kbn/apm-synthtrace-client';
-import expect from '@kbn/expect';
-import { LogCategories } from '@kbn/apm-plugin/server/routes/assistant_functions/get_log_categories';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
-import { SupertestReturnType } from '../../common/apm_api_supertest';
-
-export default function ApiTest({ getService }: FtrProviderContext) {
- const registry = getService('registry');
- const apmApiClient = getService('apmApiClient');
- const apmSynthtraceClient = getService('synthtraceEsClient');
- const logSynthtraceClient = getService('logSynthtraceEsClient');
-
- registry.when(
- 'fetching observability alerts details context for AI assistant contextual insights',
- { config: 'trial', archives: [] },
- () => {
- const start = moment().subtract(10, 'minutes').valueOf();
- const end = moment().valueOf();
- const range = timerange(start, end);
-
- describe('when no traces or logs are available', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- },
- },
- });
- });
-
- it('returns nothing', () => {
- expect(response.body.context).to.eql([]);
- });
- });
-
- describe('when traces and logs are ingested and logs are not annotated with service.name', async () => {
- before(async () => {
- await ingestTraces({ 'service.name': 'Backend', 'container.id': 'my-container-a' });
- await ingestLogs({
- 'container.id': 'my-container-a',
- 'kubernetes.pod.name': 'pod-a',
- });
- });
-
- after(async () => {
- await cleanup();
- });
-
- describe('when no params are specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- },
- },
- });
- });
-
- it('returns only 1 log category', async () => {
- expect(response.body.context).to.have.length(1);
- expect(
- (response.body.context[0]?.data as LogCategories)?.map(
- ({ errorCategory }: { errorCategory: string }) => errorCategory
- )
- ).to.eql(['Error message from container my-container-a']);
- });
- });
-
- describe('when service name is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'service.name': 'Backend',
- },
- },
- });
- });
-
- it('returns service summary', () => {
- const serviceSummary = response.body.context.find(
- ({ key }) => key === 'serviceSummary'
- );
- expect(serviceSummary?.data).to.eql({
- 'service.name': 'Backend',
- 'service.environment': ['production'],
- 'agent.name': 'java',
- 'service.version': ['1.0.0'],
- 'language.name': 'java',
- instances: 1,
- anomalies: [],
- alerts: [],
- deployments: [],
- });
- });
-
- it('returns downstream dependencies', async () => {
- const downstreamDependencies = response.body.context.find(
- ({ key }) => key === 'downstreamDependencies'
- );
- expect(downstreamDependencies?.data).to.eql([
- {
- 'span.destination.service.resource': 'elasticsearch',
- 'span.type': 'db',
- 'span.subtype': 'elasticsearch',
- },
- ]);
- });
-
- it('returns log categories', () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(logCategories?.data).to.have.length(1);
-
- const logCategory = (logCategories?.data as LogCategories)?.[0];
- expect(logCategory?.sampleMessage).to.match(
- /Error message #\d{16} from container my-container-a/
- );
- expect(logCategory?.docCount).to.be.greaterThan(0);
- expect(logCategory?.errorCategory).to.be('Error message from container my-container-a');
- });
- });
-
- describe('when container id is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'container.id': 'my-container-a',
- },
- },
- });
- });
-
- it('returns service summary', () => {
- const serviceSummary = response.body.context.find(
- ({ key }) => key === 'serviceSummary'
- );
- expect(serviceSummary?.data).to.eql({
- 'service.name': 'Backend',
- 'service.environment': ['production'],
- 'agent.name': 'java',
- 'service.version': ['1.0.0'],
- 'language.name': 'java',
- instances: 1,
- anomalies: [],
- alerts: [],
- deployments: [],
- });
- });
-
- it('returns downstream dependencies', async () => {
- const downstreamDependencies = response.body.context.find(
- ({ key }) => key === 'downstreamDependencies'
- );
- expect(downstreamDependencies?.data).to.eql([
- {
- 'span.destination.service.resource': 'elasticsearch',
- 'span.type': 'db',
- 'span.subtype': 'elasticsearch',
- },
- ]);
- });
-
- it('returns log categories', () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(logCategories?.data).to.have.length(1);
-
- const logCategory = (logCategories?.data as LogCategories)?.[0];
- expect(logCategory?.sampleMessage).to.match(
- /Error message #\d{16} from container my-container-a/
- );
- expect(logCategory?.docCount).to.be.greaterThan(0);
- expect(logCategory?.errorCategory).to.be('Error message from container my-container-a');
- });
- });
-
- describe('when non-existing container id is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'container.id': 'non-existing-container',
- },
- },
- });
- });
-
- it('returns nothing', () => {
- expect(response.body.context).to.eql([]);
- });
- });
-
- describe('when non-existing service.name is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'service.name': 'non-existing-service',
- },
- },
- });
- });
-
- it('returns empty service summary', () => {
- const serviceSummary = response.body.context.find(
- ({ key }) => key === 'serviceSummary'
- );
- expect(serviceSummary?.data).to.eql({
- 'service.name': 'non-existing-service',
- 'service.environment': [],
- instances: 1,
- anomalies: [],
- alerts: [],
- deployments: [],
- });
- });
-
- it('returns no downstream dependencies', async () => {
- const downstreamDependencies = response.body.context.find(
- ({ key }) => key === 'downstreamDependencies'
- );
- expect(downstreamDependencies).to.eql(undefined);
- });
-
- it('returns log categories', () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(logCategories?.data).to.have.length(1);
- });
- });
- });
-
- describe('when traces and logs are ingested and logs are annotated with service.name', async () => {
- before(async () => {
- await ingestTraces({ 'service.name': 'Backend', 'container.id': 'my-container-a' });
- await ingestLogs({
- 'service.name': 'Backend',
- 'container.id': 'my-container-a',
- 'kubernetes.pod.name': 'pod-a',
- });
-
- // also ingest unrelated Frontend traces and logs that should not show up in the response when fetching "Backend"-related things
- await ingestTraces({ 'service.name': 'Frontend', 'container.id': 'my-container-b' });
- await ingestLogs({
- 'service.name': 'Frontend',
- 'container.id': 'my-container-b',
- 'kubernetes.pod.name': 'pod-b',
- });
-
- // also ingest logs that are not annotated with service.name
- await ingestLogs({
- 'container.id': 'my-container-c',
- 'kubernetes.pod.name': 'pod-c',
- });
- });
-
- after(async () => {
- await cleanup();
- });
-
- describe('when no params are specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- },
- },
- });
- });
-
- it('returns no service summary', async () => {
- const serviceSummary = response.body.context.find(
- ({ key }) => key === 'serviceSummary'
- );
- expect(serviceSummary).to.be(undefined);
- });
-
- it('returns 1 log category', async () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(
- (logCategories?.data as LogCategories)?.map(
- ({ errorCategory }: { errorCategory: string }) => errorCategory
- )
- ).to.eql(['Error message from service', 'Error message from container my-container-c']);
- });
- });
-
- describe('when service name is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'service.name': 'Backend',
- },
- },
- });
- });
-
- it('returns log categories', () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(logCategories?.data).to.have.length(1);
-
- const logCategory = (logCategories?.data as LogCategories)?.[0];
- expect(logCategory?.sampleMessage).to.match(
- /Error message #\d{16} from service Backend/
- );
- expect(logCategory?.docCount).to.be.greaterThan(0);
- expect(logCategory?.errorCategory).to.be('Error message from service Backend');
- });
- });
-
- describe('when container id is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'container.id': 'my-container-a',
- },
- },
- });
- });
-
- it('returns log categories', () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(logCategories?.data).to.have.length(1);
-
- const logCategory = (logCategories?.data as LogCategories)?.[0];
- expect(logCategory?.sampleMessage).to.match(
- /Error message #\d{16} from service Backend/
- );
- expect(logCategory?.docCount).to.be.greaterThan(0);
- expect(logCategory?.errorCategory).to.be('Error message from service Backend');
- });
- });
-
- describe('when non-existing service.name is specified', async () => {
- let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>;
- before(async () => {
- response = await apmApiClient.writeUser({
- endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights',
- params: {
- query: {
- alert_started_at: new Date(end).toISOString(),
- 'service.name': 'non-existing-service',
- },
- },
- });
- });
-
- it('returns empty service summary', () => {
- const serviceSummary = response.body.context.find(
- ({ key }) => key === 'serviceSummary'
- );
- expect(serviceSummary?.data).to.eql({
- 'service.name': 'non-existing-service',
- 'service.environment': [],
- instances: 1,
- anomalies: [],
- alerts: [],
- deployments: [],
- });
- });
-
- it('does not return log categories', () => {
- const logCategories = response.body.context.find(({ key }) => key === 'logCategories');
- expect(logCategories?.data).to.have.length(1);
-
- expect(
- (logCategories?.data as LogCategories)?.map(
- ({ errorCategory }: { errorCategory: string }) => errorCategory
- )
- ).to.eql(['Error message from container my-container-c']);
- });
- });
- });
-
- async function ingestTraces(eventMetadata: {
- 'service.name': string;
- 'container.id'?: string;
- 'host.name'?: string;
- 'kubernetes.pod.name'?: string;
- }) {
- const serviceInstance = apm
- .service({
- name: eventMetadata['service.name'],
- environment: 'production',
- agentName: 'java',
- })
- .instance('my-instance');
-
- const events = range
- .interval('1m')
- .rate(1)
- .generator((timestamp) => {
- return serviceInstance
- .transaction({ transactionName: 'tx' })
- .timestamp(timestamp)
- .duration(10000)
- .defaults({ 'service.version': '1.0.0', ...eventMetadata })
- .outcome('success')
- .children(
- serviceInstance
- .span({
- spanName: 'GET apm-*/_search',
- spanType: 'db',
- spanSubtype: 'elasticsearch',
- })
- .duration(1000)
- .success()
- .destination('elasticsearch')
- .timestamp(timestamp)
- );
- });
-
- await apmSynthtraceClient.index(events);
- }
-
- function ingestLogs(eventMetadata: {
- 'service.name'?: string;
- 'container.id'?: string;
- 'kubernetes.pod.name'?: string;
- 'host.name'?: string;
- }) {
- const getMessage = () => {
- const msgPrefix = `Error message #${generateShortId()}`;
-
- if (eventMetadata['service.name']) {
- return `${msgPrefix} from service ${eventMetadata['service.name']}`;
- }
-
- if (eventMetadata['container.id']) {
- return `${msgPrefix} from container ${eventMetadata['container.id']}`;
- }
-
- if (eventMetadata['kubernetes.pod.name']) {
- return `${msgPrefix} from pod ${eventMetadata['kubernetes.pod.name']}`;
- }
-
- if (eventMetadata['host.name']) {
- return `${msgPrefix} from host ${eventMetadata['host.name']}`;
- }
-
- return msgPrefix;
- };
-
- const events = range
- .interval('1m')
- .rate(1)
- .generator((timestamp) => {
- return [
- log
- .create()
- .message(getMessage())
- .logLevel('error')
- .defaults({
- 'trace.id': generateShortId(),
- 'agent.name': 'synth-agent',
- ...eventMetadata,
- })
- .timestamp(timestamp),
- ];
- });
-
- return logSynthtraceClient.index(events);
- }
-
- async function cleanup() {
- await apmSynthtraceClient.clean();
- await logSynthtraceClient.clean();
- }
- }
- );
-}
diff --git a/x-pack/test/apm_api_integration/tests/cold_start/cold_start.spec.ts b/x-pack/test/apm_api_integration/tests/cold_start/cold_start.spec.ts
index 5d4eb4e1585c08..7d0b2f6d8a6252 100644
--- a/x-pack/test/apm_api_integration/tests/cold_start/cold_start.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/cold_start/cold_start.spec.ts
@@ -22,7 +22,7 @@ type ColdStartRate =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const { serviceName } = dataConfig;
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -74,7 +74,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
coldStartRate: 10,
@@ -85,7 +85,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', () => {
expect(status).to.be(200);
@@ -121,14 +121,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const comparisonEndDate = moment(start).add(3, 'minutes');
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start: startDate.valueOf(),
end: endDate.valueOf(),
coldStartRate: 10,
warmStartRate: 30,
});
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start: comparisonStartDate.getTime(),
end: comparisonEndDate.valueOf(),
coldStartRate: 20,
@@ -146,7 +146,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', () => {
expect(status).to.be(200);
diff --git a/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/cold_start_by_transaction_name.spec.ts b/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/cold_start_by_transaction_name.spec.ts
index 4078aaa08e793d..7019735a05498f 100644
--- a/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/cold_start_by_transaction_name.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/cold_start_by_transaction_name.spec.ts
@@ -22,7 +22,7 @@ type ColdStartRate =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const { serviceName, transactionName } = dataConfig;
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -79,7 +79,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
coldStartRate: 10,
@@ -90,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', () => {
expect(status).to.be(200);
@@ -126,14 +126,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const comparisonEndDate = moment(start).add(3, 'minutes');
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start: startDate.valueOf(),
end: endDate.valueOf(),
coldStartRate: 10,
warmStartRate: 30,
});
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start: comparisonStartDate.getTime(),
end: comparisonEndDate.valueOf(),
coldStartRate: 20,
@@ -151,7 +151,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', () => {
expect(status).to.be(200);
diff --git a/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts b/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts
index 407e115d473b7e..ff4d725a3d0177 100644
--- a/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts
@@ -14,13 +14,13 @@ export const dataConfig = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
coldStartRate,
warmStartRate,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
coldStartRate: number;
@@ -60,5 +60,5 @@ export async function generateData({
),
];
- await synthtraceEsClient.index(traceEvents);
+ await apmSynthtraceEsClient.index(traceEvents);
}
diff --git a/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts b/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts
index ecbba07e3b498e..9c36e53e826320 100644
--- a/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts
@@ -20,13 +20,13 @@ export const dataConfig = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
coldStartRate,
warmStartRate,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
coldStartRate: number;
@@ -66,5 +66,5 @@ export async function generateData({
),
];
- await synthtraceEsClient.index(traceEvents);
+ await apmSynthtraceEsClient.index(traceEvents);
}
diff --git a/x-pack/test/apm_api_integration/tests/custom_dashboards/custom_dashboards.spec.ts b/x-pack/test/apm_api_integration/tests/custom_dashboards/custom_dashboards.spec.ts
index 3dcd39df10bb86..46903bf1b5e6b4 100644
--- a/x-pack/test/apm_api_integration/tests/custom_dashboards/custom_dashboards.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/custom_dashboards/custom_dashboards.spec.ts
@@ -17,7 +17,7 @@ import {
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const start = '2023-08-22T00:00:00.000Z';
const end = '2023-08-22T00:15:00.000Z';
diff --git a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts
index dbe7db5830f933..56b310f8f2fe63 100644
--- a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts
@@ -19,7 +19,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const supertest = getService('supertest');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const logger = getService('log');
const dataViewPattern = 'traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*';
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/dependency_metrics.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/dependency_metrics.spec.ts
index 40b0120a3d96fc..c9cb6aea713063 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/dependency_metrics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/dependency_metrics.spec.ts
@@ -27,7 +27,7 @@ const {
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -98,7 +98,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Dependency metrics when data is loaded', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateOperationData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
@@ -299,6 +299,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
});
}
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts
index fad21e46f60701..58b708f0ab2538 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts
@@ -22,11 +22,11 @@ export const dataConfig = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -35,7 +35,7 @@ export async function generateData({
.instance('instance-a');
const { rate, transaction, span } = dataConfig;
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(rate)
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts b/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts
index d68183960ca5a6..97d7c35e23733c 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts
@@ -21,11 +21,11 @@ export const generateOperationDataConfig = {
export async function generateOperationData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const synthGoInstance = apm
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
@@ -36,7 +36,7 @@ export async function generateOperationData({
const interval = timerange(start, end).interval('1m');
- return await synthtraceEsClient.index([
+ return await apmSynthtraceEsClient.index([
interval
.rate(generateOperationDataConfig.ES_SEARCH_UNKNOWN_RATE)
.generator((timestamp) =>
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/metadata.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/metadata.spec.ts
index 62fb3c79588452..33b81c85991115 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/metadata.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/metadata.spec.ts
@@ -11,7 +11,7 @@ import { dataConfig, generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -47,10 +47,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
'Dependency metadata when data is generated',
{ config: 'basic', archives: [] },
() => {
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct metadata for the dependency', async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
const { status, body } = await callApi();
const { span } = dataConfig;
@@ -59,7 +59,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(body.metadata.spanType).to.equal(span.type);
expect(body.metadata.spanSubtype).to.equal(span.subType);
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
}
);
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/service_dependencies.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/service_dependencies.spec.ts
index 3ac094dc9e540d..ab9563169fe9bf 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/service_dependencies.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/service_dependencies.spec.ts
@@ -11,7 +11,7 @@ import { generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const registry = getService('registry');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -51,9 +51,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Dependency for services', { config: 'basic', archives: [] }, () => {
describe('when data is loaded', () => {
before(async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns a list of dependencies for a service', async () => {
const { status, body } = await callApi();
@@ -89,9 +89,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Dependency for services breakdown', { config: 'basic', archives: [] }, () => {
describe('when data is loaded', () => {
before(async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns a list of dependencies for a service', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts
index d08b96ab2e57f8..acdea5a3d54de0 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts
@@ -16,7 +16,7 @@ type TopDependencies = APIReturnType<'GET /internal/apm/dependencies/top_depende
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -55,12 +55,12 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let topDependencies: TopDependencies;
before(async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
const response = await callApi();
topDependencies = response.body;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns an array of dependencies', () => {
expect(topDependencies).to.have.property('dependencies');
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts
index 353c32a263eb15..20a38369bcd5da 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts
@@ -30,7 +30,7 @@ const {
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -74,13 +74,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Top operations when data is generated', { config: 'basic', archives: [] }, () => {
before(() =>
generateOperationData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
})
);
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('requested for elasticsearch', () => {
let response: TopOperations;
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts
index 1f6cae4f8393b8..b07c7c323ed9c0 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts
@@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -80,7 +80,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.instance('instance-a');
before(async () => {
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(1)
@@ -239,7 +239,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
}
);
}
diff --git a/x-pack/test/apm_api_integration/tests/dependencies/upstream_services.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/upstream_services.spec.ts
index 7a44acaf5f9e92..1a7e958881d96e 100644
--- a/x-pack/test/apm_api_integration/tests/dependencies/upstream_services.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/dependencies/upstream_services.spec.ts
@@ -11,7 +11,7 @@ import { generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const registry = getService('registry');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -51,9 +51,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Dependency upstream services', { config: 'basic', archives: [] }, () => {
describe('when data is loaded', () => {
before(async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns a list of upstream services for the dependency', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts
index d30cc24e801796..870f339ddf6029 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts
@@ -15,7 +15,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const es = getService('es');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -43,7 +43,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -57,7 +57,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns zero doc_counts when no time range is specified', async () => {
const { body } = await apmApiClient.readUser({
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts
index cd94fe5d604182..969ce9fabd5a6f 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts
@@ -13,7 +13,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const es = getService('es');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const synthtraceKibanaClient = getService('synthtraceKibanaClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -53,7 +53,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -67,7 +67,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns 5 data streams', async () => {
const { status, body } = await apmApiClient.adminUser({
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts b/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts
index 675145ab8673b8..64749066a2e04d 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts
@@ -14,7 +14,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const es = getService('es');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const synthtraceKibanaClient = getService('synthtraceKibanaClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -49,7 +49,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const instance = apm
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -63,7 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns APM index templates', async () => {
const { status, body } = await apmApiClient.adminUser({
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts
index fcde133347ee3a..2ece2835b59444 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts
@@ -14,7 +14,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const es = getService('es');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const synthtraceKibanaClient = getService('synthtraceKibanaClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -47,7 +47,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const instance = apm
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -61,7 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('verifies that all the default APM index templates exist', async () => {
const { status, body } = await apmApiClient.adminUser({
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts
index 4f7507e74e2f3e..477824524b48c0 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts
@@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const es = getService('es');
const synthtraceKibanaClient = getService('synthtraceKibanaClient');
@@ -40,7 +40,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -54,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns empty response', async () => {
const { status, body } = await apmApiClient.adminUser({
@@ -76,7 +76,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -94,7 +94,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await es.indices.delete({ index: 'traces-apm-default' });
const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion();
await synthtraceKibanaClient.installApmPackage(latestVersion);
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
it('returns a list of items with mapping issues', async () => {
@@ -121,7 +121,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-go', environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(30)
@@ -138,7 +138,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion();
await synthtraceKibanaClient.installApmPackage(latestVersion);
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
describe.skip('an ingest pipeline is removed', () => {
diff --git a/x-pack/test/apm_api_integration/tests/environment/generate_data.ts b/x-pack/test/apm_api_integration/tests/environment/generate_data.ts
index 6e9ae2831e6be9..af71533bf3e0d5 100644
--- a/x-pack/test/apm_api_integration/tests/environment/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/environment/generate_data.ts
@@ -10,11 +10,11 @@ import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
// Generate synthetic data for the environment test suite
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -63,5 +63,5 @@ export async function generateData({
return [...loopGeneratedDocs, customDoc];
});
- await synthtraceEsClient.index(docs);
+ await apmSynthtraceEsClient.index(docs);
}
diff --git a/x-pack/test/apm_api_integration/tests/environment/get_environment.spec.ts b/x-pack/test/apm_api_integration/tests/environment/get_environment.spec.ts
index bdb24342c8653e..8a717e735b0f23 100644
--- a/x-pack/test/apm_api_integration/tests/environment/get_environment.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/environment/get_environment.spec.ts
@@ -17,19 +17,19 @@ const end = new Date(endNumber).toISOString();
export default function environmentsAPITests({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
// FLAKY: https://github.com/elastic/kibana/issues/177305
registry.when('environments when data is loaded', { config: 'basic', archives: [] }, async () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start: startNumber,
end: endNumber,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('get environments', () => {
describe('when service name is not specified', () => {
diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts
index 9a0ba400e13da6..2ab6b1bb97a5ed 100644
--- a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts
@@ -17,7 +17,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -166,7 +166,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const transactionNameProductList = 'GET /api/product/list';
const transactionNameProductId = 'GET /api/product/:id';
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_LIST_RATE)
@@ -210,7 +210,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('compare error rate value between service inventory, error rate chart, service inventory and transactions apis', () => {
before(async () => {
diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts
index fa4b3f7074352b..aa7d635b977cc9 100644
--- a/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts
@@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -88,7 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const transactionNameProductList = 'GET /api/product/list';
const transactionNameProductId = 'GET /api/product/:id';
- return synthtraceEsClient.index([
+ return apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_LIST_RATE)
@@ -138,7 +138,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
// FLAKY: https://github.com/elastic/kibana/issues/172772
describe('compare latency value between service inventory and service maps', () => {
diff --git a/x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts b/x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts
index 2b30dca5db80a1..544ca97817af04 100644
--- a/x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/distribution.spec.ts
@@ -21,7 +21,7 @@ type ErrorsDistribution =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -65,10 +65,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('errors distribution', () => {
const { appleTransaction, bananaTransaction } = config;
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('without comparison', () => {
let errorsDistribution: ErrorsDistribution;
diff --git a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts
index bac97c76a3f362..fd01833cb4f502 100644
--- a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts
@@ -19,7 +19,7 @@ type ErrorGroups =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -73,7 +73,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(appleTransaction.successRate)
@@ -123,7 +123,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('returns the correct data', () => {
let errorGroups: ErrorGroups;
diff --git a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts
index 1ecc0f02e3f39e..a7e627a048e053 100644
--- a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts
@@ -21,12 +21,12 @@ export const config = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -69,5 +69,5 @@ export async function generateData({
];
});
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
diff --git a/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts
index 165fb81e3c643d..004f853b6c56af 100644
--- a/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts
@@ -22,7 +22,7 @@ type ErrorSampleDetails =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -80,10 +80,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const { bananaTransaction } = config;
describe('error group id', () => {
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('return correct data', () => {
let errorsSamplesResponse: ErrorGroupSamples;
@@ -108,10 +108,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('when error sample data is loaded', { config: 'basic', archives: [] }, () => {
describe('error sample id', () => {
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('return correct data', () => {
let errorSampleDetailsResponse: ErrorSampleDetails;
@@ -146,7 +146,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const errorMessage = 'Error 1';
const groupId = getErrorGroupingKey(errorMessage);
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('15m')
.rate(1)
@@ -174,7 +174,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns the errors in the correct order (sampled first, then unsampled)', () => {
const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => parseInt(id, 10));
diff --git a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts
index 47c48693eaa0e4..7732d85efa58ff 100644
--- a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts
@@ -21,12 +21,12 @@ export const config = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -69,5 +69,5 @@ export async function generateData({
];
});
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
diff --git a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts
index e22099af5cbbe5..ff985e0af388fa 100644
--- a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts
@@ -21,7 +21,7 @@ type ErroneousTransactions =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -73,10 +73,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('returns the correct data', () => {
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('without comparison', () => {
const numberOfBuckets = 15;
diff --git a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts
index 6026f762292c3d..9f983fbb8877be 100644
--- a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts
@@ -21,12 +21,12 @@ export const config = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -72,5 +72,5 @@ export async function generateData({
];
});
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
diff --git a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts
index 9bb171ff64f308..8e946e081554fc 100644
--- a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts
@@ -20,7 +20,7 @@ type ErrorGroups =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -66,10 +66,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
} = config;
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('returns the correct data', () => {
const NUMBER_OF_BUCKETS = 15;
diff --git a/x-pack/test/apm_api_integration/tests/fleet/input_only_package.spec.ts b/x-pack/test/apm_api_integration/tests/fleet/input_only_package.spec.ts
index 5d02209ca0aa6e..b7d90ff9713cef 100644
--- a/x-pack/test/apm_api_integration/tests/fleet/input_only_package.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/fleet/input_only_package.spec.ts
@@ -33,7 +33,7 @@ export default function ApiTest(ftrProviderContext: FtrProviderContext) {
const bettertest = getBettertest(supertest);
const config = getService('config');
const synthtraceKibanaClient = getService('synthtraceKibanaClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const API_KEY_NAME = 'apm_api_key_testing';
const APM_AGENT_POLICY_NAME = 'apm_agent_policy_testing';
@@ -104,7 +104,7 @@ export default function ApiTest(ftrProviderContext: FtrProviderContext) {
async function cleanAll() {
try {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
await es.security.invalidateApiKey({ name: API_KEY_NAME });
await deleteAgentPolicyAndPackagePolicyByName({
bettertest,
diff --git a/x-pack/test/apm_api_integration/tests/historical_data/has_data.spec.ts b/x-pack/test/apm_api_integration/tests/historical_data/has_data.spec.ts
index 440fe7fd39b3b1..e0b5a0e076ffd4 100644
--- a/x-pack/test/apm_api_integration/tests/historical_data/has_data.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/historical_data/has_data.spec.ts
@@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
// FLAKY: https://github.com/elastic/kibana/issues/177385
registry.when('Historical data ', { config: 'basic', archives: [] }, () => {
@@ -45,10 +45,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
),
];
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns hasData=true', async () => {
const response = await apmApiClient.readUser({ endpoint: `GET /internal/apm/has_data` });
diff --git a/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts b/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts
index eb9a34f6e1f631..437c4791e19723 100644
--- a/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts
@@ -8,11 +8,11 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -24,7 +24,7 @@ export async function generateData({
.service({ name: 'synth-java', environment: 'production', agentName: 'java' })
.instance('instance-b');
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.generator((timestamp) => {
diff --git a/x-pack/test/apm_api_integration/tests/infrastructure/infrastructure_attributes.spec.ts b/x-pack/test/apm_api_integration/tests/infrastructure/infrastructure_attributes.spec.ts
index e1b0d3c66cbaae..7a79e2f8be4b10 100644
--- a/x-pack/test/apm_api_integration/tests/infrastructure/infrastructure_attributes.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/infrastructure/infrastructure_attributes.spec.ts
@@ -11,7 +11,7 @@ import { generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -52,10 +52,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Infrastructure attributes', { config: 'basic', archives: [] }, () => {
describe('when data is loaded', () => {
beforeEach(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
});
- afterEach(() => synthtraceEsClient.clean());
+ afterEach(() => apmSynthtraceEsClient.clean());
describe('when service runs in container', () => {
it('returns arrays of container ids and pod names', async () => {
diff --git a/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts
index 397c2f107ccd9d..35ef23c8a44303 100644
--- a/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts
@@ -17,7 +17,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -170,7 +170,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'development', agentName: 'go' })
.instance('instance-b');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -192,7 +192,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('compare latency value between service inventory, latency chart, service inventory and transactions apis', () => {
before(async () => {
diff --git a/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts
index 6796e448d216d2..298fde675bc4ac 100644
--- a/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts
@@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -87,7 +87,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'development', agentName: 'go' })
.instance('instance-b');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -112,7 +112,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
// FLAKY: https://github.com/elastic/kibana/issues/176976
describe('compare latency value between service inventory and service maps', () => {
diff --git a/x-pack/test/apm_api_integration/tests/metrics/memory/generate_data.ts b/x-pack/test/apm_api_integration/tests/metrics/memory/generate_data.ts
index 22abd719892064..c5bc09bc6f67b8 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/memory/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/memory/generate_data.ts
@@ -22,11 +22,11 @@ export const expectedValues = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -73,5 +73,5 @@ export async function generateData({
.timestamp(timestamp),
]);
- await synthtraceEsClient.index(transactionsEvents);
+ await apmSynthtraceEsClient.index(transactionsEvents);
}
diff --git a/x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts
index 5af51dff5604cd..e0f8fc1cf28c28 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts
@@ -12,7 +12,7 @@ import { config, generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -36,10 +36,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// FLAKY: https://github.com/elastic/kibana/issues/176990
registry.when('Memory', { config: 'trial', archives: [] }, () => {
before(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns system memory stats', async () => {
const expectedFreeMemory = 1 - config.memoryFree / config.memoryTotal;
diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts
index e6a7cc57f06985..b835201d51564f 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts
@@ -26,11 +26,11 @@ export const expectedValues = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -116,5 +116,5 @@ export async function generateData({
.success(),
]);
- await synthtraceEsClient.index(transactionsEvents);
+ await apmSynthtraceEsClient.index(transactionsEvents);
}
diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts
index cbb6317c3bd756..1b15e03c919872 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts
@@ -14,7 +14,7 @@ import { config, expectedValues, generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -49,10 +49,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const { expectedMemoryUsed } = expectedValues;
before(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('Python service', () => {
let activeInstances: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>;
diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts
index 133953dd9772fa..94792228a2859b 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts
@@ -13,7 +13,7 @@ import { config, expectedValues, generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -46,10 +46,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const { expectedMemoryUsed } = expectedValues;
before(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('Python service', () => {
let functionsOverview: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview'>;
diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts
index b94d63e491ffd6..192fb3c87c48b8 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts
@@ -21,7 +21,7 @@ function isNotNullOrZeroCoordinate(coordinate: Coordinate) {
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -77,10 +77,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
} = config;
before(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('Python service', () => {
let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>;
diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts
index 0f013ca69776bb..727c7eee1e3cdd 100644
--- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts
@@ -13,7 +13,7 @@ import { config, expectedValues, generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -55,10 +55,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const { expectedMemoryUsedRate } = expectedValues;
before(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('Python service', () => {
let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts
index 05897e7c22cd3b..274199437f188c 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts
@@ -19,7 +19,7 @@ type ErrorGroups =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-swift';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -73,7 +73,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'production', agentName: 'swift' })
.instance('instance-a');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(appleTransaction.successRate)
@@ -131,7 +131,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('returns the correct data', () => {
let errorGroups: ErrorGroups;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts
index d098d83f7a19ee..aad32f3490d2aa 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts
@@ -21,7 +21,7 @@ type ErrorsDistribution =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-swift';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -65,10 +65,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('errors distribution', () => {
const { appleTransaction, bananaTransaction } = config;
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('without comparison', () => {
let errorsDistribution: ErrorsDistribution;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts
index 606d97fb9ce04d..1e25cef4d3fd39 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/generate_data.ts
@@ -21,12 +21,12 @@ export const config = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -72,5 +72,5 @@ export async function generateData({
];
});
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts b/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts
index 663849f274adbd..c1ae3723358246 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/errors/generate_data.ts
@@ -21,12 +21,12 @@ export const config = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -69,5 +69,5 @@ export async function generateData({
];
});
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
}
diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts
index 29413e79ffd74e..e3e69a540881c3 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts
@@ -22,7 +22,7 @@ type ErrorSampleDetails =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -80,10 +80,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const { bananaTransaction } = config;
describe('error group id', () => {
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('return correct data', () => {
let errorsSamplesResponse: ErrorGroupSamples;
@@ -108,10 +108,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('when error sample data is loaded', { config: 'basic', archives: [] }, () => {
describe('error sample id', () => {
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('return correct data', () => {
let errorSampleDetailsResponse: ErrorSampleDetails;
@@ -146,7 +146,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const errorMessage = 'Error 1';
const groupId = getErrorGroupingKey(errorMessage);
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('15m')
.rate(1)
@@ -174,7 +174,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
errorGroupSamplesResponse = (await callErrorGroupSamplesApi({ groupId })).body;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns the errors in the correct order (sampled first, then unsampled)', () => {
const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => parseInt(id, 10));
diff --git a/x-pack/test/apm_api_integration/tests/mobile/generate_mobile_data.ts b/x-pack/test/apm_api_integration/tests/mobile/generate_mobile_data.ts
index 91a8aac9bc3d36..a4420b3f53c7c4 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/generate_mobile_data.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/generate_mobile_data.ts
@@ -12,11 +12,11 @@ export const SERVICE_VERSIONS = ['2.3', '1.2', '1.1'];
export async function generateMobileData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const galaxy10 = apm
.mobileApp({
@@ -210,7 +210,7 @@ export async function generateMobileData({
})
.setNetworkConnection({ type: 'wifi' });
- return await synthtraceEsClient.index([
+ return await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts
index 0e128ba6b102f7..40bf9729bee6ed 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts
@@ -19,7 +19,7 @@ type MobileDetailedStatisticsResponse =
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -79,13 +79,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
() => {
before(async () => {
await generateMobileData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when comparison is disable', () => {
it('returns current period data only', async () => {
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts
index 2dbe88baf179ec..42862668016098 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts
@@ -17,11 +17,11 @@ type MobileFilters = APIReturnType<'GET /internal/apm/services/{serviceName}/mob
async function generateData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const galaxy10 = apm
.mobileApp({
@@ -88,7 +88,7 @@ async function generateData({
carrierMCC: '440',
});
- return await synthtraceEsClient.index([
+ return await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -136,7 +136,7 @@ async function generateData({
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -181,13 +181,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Mobile filters', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
let response: MobileFilters;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts
index b924af87d3e207..4c661c9ae14f65 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts
@@ -13,7 +13,7 @@ import { generateMobileData } from './generate_mobile_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T02:00:00.000Z').getTime();
@@ -66,13 +66,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Mobile HTTP requests with data loaded', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateMobileData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
it('returns timeseries for http requests chart', async () => {
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts
index 8e6e7d7cfd52eb..0acf17308b0d83 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts
@@ -22,11 +22,11 @@ type MobileLocationStats =
async function generateData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const galaxy10 = apm
.mobileApp({
@@ -130,7 +130,7 @@ async function generateData({
carrierMCC: '440',
});
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -179,7 +179,7 @@ async function generateData({
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -236,13 +236,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Location stats', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
let response: MobileLocationStats;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts
index 57832797b37984..a3f95eaeb495d8 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts
@@ -30,11 +30,11 @@ function calculateThroughput({ start, end }: { start: number; end: number }) {
async function generateData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const galaxy10 = apm
.mobileApp({
@@ -101,7 +101,7 @@ async function generateData({
carrierMCC: '440',
});
- return await synthtraceEsClient.index([
+ return await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -129,7 +129,7 @@ async function generateData({
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -182,13 +182,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Mobile main statistics', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
const huaweiLatency = calculateLatency(HUAWEI_DURATION);
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts
index b4d595a7283561..497e6987a3e2fb 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts
@@ -17,7 +17,7 @@ type MostUsedCharts =
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -68,13 +68,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Mobile stats', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateMobileData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
let response: MostUsedCharts;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts
index d9d70e3ede65f4..99f0f245c8c4cc 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts
@@ -13,7 +13,7 @@ import { generateMobileData } from './generate_mobile_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T02:00:00.000Z').getTime();
@@ -62,13 +62,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('with data loaded', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateMobileData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
it('returns timeseries for sessions chart', async () => {
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts
index 8de8fea4a55366..22b7d0c8b8f65b 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts
@@ -22,11 +22,11 @@ type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName
async function generateData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const galaxy10 = apm
.mobileApp({
@@ -93,7 +93,7 @@ async function generateData({
carrierMCC: '440',
});
- return await synthtraceEsClient.index([
+ return await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -137,7 +137,7 @@ async function generateData({
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -188,13 +188,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Mobile stats', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
let response: MobileStats;
diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts
index 80f8ed366dd522..d50371423c1668 100644
--- a/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts
@@ -17,11 +17,11 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
async function generateData({
start,
end,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
}: {
start: number;
end: number;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
}) {
const galaxy10 = apm
.mobileApp({
@@ -88,7 +88,7 @@ async function generateData({
carrierMCC: '440',
});
- return await synthtraceEsClient.index([
+ return await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -127,7 +127,7 @@ async function generateData({
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -189,13 +189,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Mobile terms', { config: 'basic', archives: [] }, () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('when data is loaded', () => {
it('returns mobile devices', async () => {
diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts
index a72ca0539c7af1..763d8eee929d28 100644
--- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts
@@ -16,7 +16,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -106,7 +106,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-java', environment: 'production', agentName: 'java' })
.instance('instance-c');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -137,7 +137,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('compare throughput values', () => {
let throughputValues: Awaited>;
diff --git a/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/generate_data.ts b/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/generate_data.ts
index 292307d7cc1e3a..7f9b1487bb8ef7 100644
--- a/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/generate_data.ts
@@ -8,11 +8,11 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -28,7 +28,7 @@ export async function generateData({
.instance('instance-3'),
];
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
synthServices.map((service) =>
timerange(start, end)
.interval('5m')
diff --git a/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts b/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts
index 427edcf0b16142..044006e2733485 100644
--- a/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts
@@ -22,7 +22,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const supertest = getService('supertest');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const es = getService('es');
const log = getService('log');
const start = Date.now() - 24 * 60 * 60 * 1000;
@@ -52,7 +52,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(async () => {
const [, { body: synthbeansServiceGroup }, { body: opbeansServiceGroup }] = await Promise.all(
[
- generateData({ start, end, synthtraceEsClient }),
+ generateData({ start, end, apmSynthtraceEsClient }),
createServiceGroupApi({
apmApiClient,
groupName: 'synthbeans',
@@ -71,7 +71,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
await deleteAllServiceGroups(apmApiClient);
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
it('returns the correct number of services', async () => {
diff --git a/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/generate_data.ts b/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/generate_data.ts
index e688e6ac6836a4..b9fe538d580dd1 100644
--- a/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/generate_data.ts
@@ -8,11 +8,11 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: string;
end: string;
}) {
@@ -25,7 +25,7 @@ export async function generateData({
.instance('instance-2'),
];
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
synthServices.map((service) =>
timerange(start, end)
.interval('5m')
diff --git a/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts b/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts
index 26d6b54025cca0..93b873143599a7 100644
--- a/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts
@@ -18,7 +18,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const es = getService('es');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
registry.when(
'Display overflow bucket in Service Groups',
@@ -33,11 +33,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
await deleteAllServiceGroups(apmApiClient);
- synthtraceEsClient.clean();
+ apmSynthtraceEsClient.clean();
});
before(async () => {
- await generateData({ start, end, synthtraceEsClient });
+ await generateData({ start, end, apmSynthtraceEsClient });
const docs = [
createServiceTransactionMetricsDocs({
diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts
index aacee3d7393be2..4be91cbe7bab61 100644
--- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts
@@ -16,7 +16,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
@@ -68,10 +68,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
})
);
- await synthtraceEsClient.index(events);
+ await apmSynthtraceEsClient.index(events);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns full service map when no kuery is defined', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts
index 4806f344219bbb..6b24587b6bc138 100644
--- a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts
@@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -54,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const instance = apm
.service({ name: serviceName, environment: 'production', agentName: 'go' })
.instance(instanceName);
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(1)
@@ -70,7 +70,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
)
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns service nodes', async () => {
const response = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instance_details.spec.ts b/x-pack/test/apm_api_integration/tests/service_overview/instance_details.spec.ts
index cdc613ddcbeb8f..013237934904f4 100644
--- a/x-pack/test/apm_api_integration/tests/service_overview/instance_details.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/service_overview/instance_details.spec.ts
@@ -17,7 +17,7 @@ type ServiceOverviewInstanceDetails =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const start = '2023-08-22T00:00:00.000Z';
const end = '2023-08-22T01:00:00.000Z';
diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts
index 1f53d20a978897..b0a6bf51d1ccfa 100644
--- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts
@@ -25,7 +25,7 @@ type ServiceOverviewInstancesMainStatistics =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:10:00.000Z').getTime();
diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts
index 9ec17ff8374ada..620d705f4463ff 100644
--- a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts
@@ -24,7 +24,7 @@ type ErrorGroupsDetailedStatistics =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -69,10 +69,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('when data is loaded', () => {
const { PROD_LIST_ERROR_RATE, PROD_ID_ERROR_RATE } = config;
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('without data comparison', () => {
let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics;
diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts
index 7096b65b1dbfae..3377cdabb38474 100644
--- a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts
@@ -20,7 +20,7 @@ type ErrorGroupsMainStatistics =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -64,10 +64,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const { PROD_LIST_ERROR_RATE, PROD_ID_ERROR_RATE, ERROR_NAME_1, ERROR_NAME_2 } = config;
before(async () => {
- await generateData({ serviceName, start, end, synthtraceEsClient });
+ await generateData({ serviceName, start, end, apmSynthtraceEsClient });
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('returns the correct data', () => {
let errorGroupMainStatistics: ErrorGroupsMainStatistics;
diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts
index 01421a73b8d272..33d1ad4fc566f3 100644
--- a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts
@@ -17,12 +17,12 @@ export const config = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
serviceName,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
serviceName: string;
start: number;
end: number;
@@ -43,7 +43,7 @@ export async function generateData({
ERROR_NAME_2,
} = config;
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(PROD_LIST_RATE)
diff --git a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts
index 5203c2c4634018..6644ed8bc7d1cf 100644
--- a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts
@@ -14,7 +14,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -66,7 +66,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const instance = apm
.service({ name: serviceName, environment: 'production', agentName: 'go' })
.instance(instanceName);
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
timerange(start, end)
.interval('1m')
.rate(1)
@@ -80,7 +80,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
)
);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns service node metadata', async () => {
const response = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts b/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts
index 2d32e0a85ffde4..958c2ed88f4604 100644
--- a/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts
@@ -18,7 +18,7 @@ export default function ServiceAlerts({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const supertest = getService('supertest');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const es = getService('es');
const dayInMs = 24 * 60 * 60 * 1000;
const start = Date.now() - dayInMs;
@@ -73,7 +73,7 @@ export default function ServiceAlerts({ getService }: FtrProviderContext) {
.instance('instance-1'),
];
- await synthtraceEsClient.index(
+ await apmSynthtraceEsClient.index(
synthServices.map((service) =>
timerange(start, end)
.interval('5m')
@@ -114,7 +114,7 @@ export default function ServiceAlerts({ getService }: FtrProviderContext) {
});
after(async () => {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
describe('with alerts', () => {
diff --git a/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts
index 811e0c2b9ede19..653f0113ba2851 100644
--- a/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts
@@ -43,11 +43,11 @@ export const dataConfig = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -127,5 +127,5 @@ export async function generateData({
),
];
- await synthtraceEsClient.index(traceEvents);
+ await apmSynthtraceEsClient.index(traceEvents);
}
diff --git a/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts b/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts
index 9ba86c29943ae1..4d8b250ec623c3 100644
--- a/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts
@@ -16,7 +16,7 @@ type ServiceDetails = APIReturnType<'GET /internal/apm/services/{serviceName}/me
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const {
service: { name: serviceName },
@@ -57,13 +57,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let status: number;
before(async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
const response = await callApi();
body = response.body;
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', () => {
expect(status).to.be(200);
diff --git a/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts
index ddfe8b57286566..bc842d2342c58f 100644
--- a/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts
@@ -22,11 +22,11 @@ export const dataConfig = {
};
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -53,5 +53,5 @@ export async function generateData({
.success()
);
- await synthtraceEsClient.index(traceEvents);
+ await apmSynthtraceEsClient.index(traceEvents);
}
diff --git a/x-pack/test/apm_api_integration/tests/services/service_icons/service_icons.spec.ts b/x-pack/test/apm_api_integration/tests/services/service_icons/service_icons.spec.ts
index ee7e31f7c81dd7..3516edd1800cbd 100644
--- a/x-pack/test/apm_api_integration/tests/services/service_icons/service_icons.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/service_icons/service_icons.spec.ts
@@ -16,7 +16,7 @@ type ServiceIconMetadata = APIReturnType<'GET /internal/apm/services/{serviceNam
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const { serviceName } = dataConfig;
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -50,13 +50,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let status: number;
before(async () => {
- await generateData({ synthtraceEsClient, start, end });
+ await generateData({ apmSynthtraceEsClient, start, end });
const response = await callApi();
body = response.body;
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', () => {
expect(status).to.be(200);
diff --git a/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.spec.ts
index 44ac364bdf7d4c..0a33450e7f9804 100644
--- a/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.spec.ts
@@ -23,7 +23,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const start = '2021-01-01T00:00:00.000Z';
const end = '2021-01-01T00:59:59.999Z';
diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts b/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts
index 624706d30115ae..d8b3227890f1da 100644
--- a/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts
@@ -26,7 +26,7 @@ type ThroughputReturn = APIReturnType<'GET /internal/apm/services/{serviceName}/
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -97,7 +97,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-java', environment: 'development', agentName: 'java' })
.instance('instance-c');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -128,7 +128,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('compare transactions and metrics based throughput', () => {
let throughputMetrics: ThroughputReturn;
diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts b/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts
index d35e28d30aec7c..48b394c0926387 100644
--- a/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts
@@ -20,7 +20,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const archiveName = 'apm_8.0.0';
diff --git a/x-pack/test/apm_api_integration/tests/services/transaction_types.spec.ts b/x-pack/test/apm_api_integration/tests/services/transaction_types.spec.ts
index b8e5586d2efad8..57dbbb1c6ee49f 100644
--- a/x-pack/test/apm_api_integration/tests/services/transaction_types.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/services/transaction_types.spec.ts
@@ -14,7 +14,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const start = '2023-10-28T00:00:00.000Z';
const end = '2023-10-28T00:14:59.999Z';
diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration/add_agent_config_metrics.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration/add_agent_config_metrics.ts
index b2fe4fd6099962..dc915f3accb791 100644
--- a/x-pack/test/apm_api_integration/tests/settings/agent_configuration/add_agent_config_metrics.ts
+++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration/add_agent_config_metrics.ts
@@ -9,14 +9,14 @@ import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import { Readable } from 'stream';
export function addAgentConfigEtagMetric({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
timestamp,
etag,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
timestamp: number;
etag: string;
}) {
const agentConfigMetric = observer().agentConfig().etag(etag).timestamp(timestamp);
- return synthtraceEsClient.index(Readable.from([agentConfigMetric]));
+ return apmSynthtraceEsClient.index(Readable.from([agentConfigMetric]));
}
diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts
index 431eca227d6058..98a971610a1495 100644
--- a/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration/agent_configuration.spec.ts
@@ -20,7 +20,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte
const apmApiClient = getService('apmApiClient');
const log = getService('log');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const archiveName = 'apm_8.0.0';
@@ -416,13 +416,13 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte
describe('when there are agent config metrics for this etag', () => {
before(async () => {
await addAgentConfigEtagMetric({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
timestamp: Date.now(),
etag: agentConfiguration.etag,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it(`should have 'applied_by_agent=true' when getting a config from all configurations`, async () => {
const {
diff --git a/x-pack/test/apm_api_integration/tests/span_links/span_links.spec.ts b/x-pack/test/apm_api_integration/tests/span_links/span_links.spec.ts
index d9488107d9d5f7..871f44da4cdc1a 100644
--- a/x-pack/test/apm_api_integration/tests/span_links/span_links.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/span_links/span_links.spec.ts
@@ -13,7 +13,7 @@ import { generateSpanLinksData } from './data_generator';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2022-01-01T00:00:00.000Z').getTime();
const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1;
@@ -27,7 +27,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
ids = spanLinksData.ids;
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
Readable.from(spanLinksData.events.producerInternalOnly),
Readable.from(spanLinksData.events.producerExternalOnly),
Readable.from(spanLinksData.events.producerConsumer),
@@ -35,7 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('Span links count on traces', () => {
async function fetchTraces({
diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts
index 70f9ab369dcf54..01d258a18717af 100644
--- a/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts
@@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const apmApiClient = getService('apmApiClient');
const start = '2021-01-01T12:00:00.000Z';
@@ -84,10 +84,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
serviceC.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp)
);
- await synthtraceEsClient.index([eventsWithinTimerange, eventsOutsideOfTimerange]);
+ await apmSynthtraceEsClient.index([eventsWithinTimerange, eventsOutsideOfTimerange]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('with no kuery, environment or index lifecycle phase set it returns services based on the terms enum API', async () => {
const items = await getServices();
diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts
index 8c9dc354356a95..84f9ca4e35e977 100644
--- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts
@@ -21,7 +21,7 @@ type StorageDetails = APIReturnType<'GET /internal/apm/services/{serviceName}/st
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -78,7 +78,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'production', agentName: 'go' })
.instance('instance');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -110,7 +110,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it.skip('returns correct stats for processor events', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts
index f6ade43b4e29fc..6f90d35c6ebff5 100644
--- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts
@@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -72,7 +72,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: nodeServiceName, environment: 'dev', agentName: 'node' })
.instance('instance-node-dev');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -103,7 +103,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct stats', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts
index cab335bded51f9..97375f0b685e26 100644
--- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts
@@ -15,7 +15,7 @@ import { roundNumber } from '../../utils';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -73,7 +73,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: nodeServiceName, environment: 'dev', agentName: 'node' })
.instance('instance-node');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(1)
@@ -95,7 +95,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct summary stats', async () => {
const { status, body } = await callApi();
diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts
index 757d9be55ee716..64f94bf3900df5 100644
--- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts
@@ -16,7 +16,7 @@ type StorageTimeSeries = APIReturnType<'GET /internal/apm/storage_chart'>;
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -64,7 +64,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-go-2', environment: 'production', agentName: 'go' })
.instance('instance');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('5m')
.rate(1)
@@ -90,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
status = response.status;
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns correct HTTP status', async () => {
expect(status).to.be(200);
diff --git a/x-pack/test/apm_api_integration/tests/suggestions/generate_data.ts b/x-pack/test/apm_api_integration/tests/suggestions/generate_data.ts
index 283c907782cdfa..0311861436ec1e 100644
--- a/x-pack/test/apm_api_integration/tests/suggestions/generate_data.ts
+++ b/x-pack/test/apm_api_integration/tests/suggestions/generate_data.ts
@@ -9,11 +9,11 @@ import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import { times } from 'lodash';
export async function generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start,
end,
}: {
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
@@ -73,5 +73,5 @@ export async function generateData({
return [...autoGeneratedDocs, customDoc];
});
- return await synthtraceEsClient.index(docs);
+ return await apmSynthtraceEsClient.index(docs);
}
diff --git a/x-pack/test/apm_api_integration/tests/suggestions/suggestions.spec.ts b/x-pack/test/apm_api_integration/tests/suggestions/suggestions.spec.ts
index 932f6f56360164..d4d1c3b1417002 100644
--- a/x-pack/test/apm_api_integration/tests/suggestions/suggestions.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/suggestions/suggestions.spec.ts
@@ -23,19 +23,19 @@ const end = new Date(endNumber).toISOString();
export default function suggestionsTests({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
// FLAKY: https://github.com/elastic/kibana/issues/177538
registry.when('suggestions when data is loaded', { config: 'basic', archives: [] }, async () => {
before(async () => {
await generateData({
- synthtraceEsClient,
+ apmSynthtraceEsClient,
start: startNumber,
end: endNumber,
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe(`field: ${SERVICE_ENVIRONMENT}`, () => {
describe('when fieldValue is empty', () => {
diff --git a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts
index 3edf6946df9a6c..fe591631fafe74 100644
--- a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts
@@ -14,7 +14,7 @@ import { roundNumber } from '../../utils';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
@@ -106,7 +106,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'synth-java', environment: 'development', agentName: 'java' })
.instance('instance-c');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -174,7 +174,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('verify top dependencies', () => {
before(async () => {
diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts
index eb8019d3b358f1..9d69ce74bf0ead 100644
--- a/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts
@@ -17,7 +17,7 @@ import { roundNumber } from '../../utils';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -154,7 +154,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'development', agentName: 'go' })
.instance('instance-b');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -176,7 +176,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('compare throughput value between service inventory, throughput chart, service inventory and transactions apis', () => {
before(async () => {
diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts
index e502e4ea66066e..5ee475344e2867 100644
--- a/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts
@@ -15,7 +15,7 @@ import { roundNumber } from '../../utils';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -95,7 +95,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'development', agentName: 'go' })
.instance('instance-b');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -117,7 +117,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
// FLAKY: https://github.com/elastic/kibana/issues/176984
describe('compare throughput value between service inventory and service maps', () => {
diff --git a/x-pack/test/apm_api_integration/tests/time_range_metadata/many_apm_server_versions.spec.ts b/x-pack/test/apm_api_integration/tests/time_range_metadata/many_apm_server_versions.spec.ts
index 8c3ffbd6528153..01b4b355308368 100644
--- a/x-pack/test/apm_api_integration/tests/time_range_metadata/many_apm_server_versions.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/time_range_metadata/many_apm_server_versions.spec.ts
@@ -26,7 +26,7 @@ import { ApmApiClient } from '../../common/config';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtrace = getService('synthtraceEsClient');
+ const synthtrace = getService('apmSynthtraceEsClient');
const es = getService('es');
const baseTime = new Date('2023-10-01T00:00:00.000Z').getTime();
diff --git a/x-pack/test/apm_api_integration/tests/time_range_metadata/time_range_metadata.spec.ts b/x-pack/test/apm_api_integration/tests/time_range_metadata/time_range_metadata.spec.ts
index b46b6c98fb1dc6..43aee07583c180 100644
--- a/x-pack/test/apm_api_integration/tests/time_range_metadata/time_range_metadata.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/time_range_metadata/time_range_metadata.spec.ts
@@ -23,7 +23,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const es = getService('es');
const log = getService('log');
@@ -95,7 +95,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
start: withoutSummaryFieldStart,
end: withoutSummaryFieldEnd,
isLegacy: true,
- synthtrace: synthtraceEsClient,
+ synthtrace: apmSynthtraceEsClient,
logger: log,
});
@@ -103,13 +103,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
start: withSummaryFieldStart,
end: withSummaryFieldEnd,
isLegacy: false,
- synthtrace: synthtraceEsClient,
+ synthtrace: apmSynthtraceEsClient,
logger: log,
});
});
after(() => {
- return synthtraceEsClient.clean();
+ return apmSynthtraceEsClient.clean();
});
describe('Values for hasDurationSummaryField for transaction metrics', () => {
@@ -155,7 +155,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(() => {
const instance = apm.service('my-service', 'production', 'java').instance('instance');
- return synthtraceEsClient.index(
+ return apmSynthtraceEsClient.index(
timerange(moment(start).subtract(1, 'day'), end)
.interval('1m')
.rate(1)
@@ -166,7 +166,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
after(() => {
- return synthtraceEsClient.clean();
+ return apmSynthtraceEsClient.clean();
});
describe('with default settings', () => {
@@ -508,7 +508,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
after(() => {
- return synthtraceEsClient.clean();
+ return apmSynthtraceEsClient.clean();
});
}
);
diff --git a/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts b/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts
index baa194a7ab56af..6d55c55ba6dbf2 100644
--- a/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts
@@ -16,7 +16,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2022-01-01T00:00:00.000Z').getTime();
const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1;
@@ -63,7 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const traceIds = compact(uniq(serialized.map((event) => event['trace.id'])));
- await synthtraceEsClient.index(Readable.from(unserialized));
+ await apmSynthtraceEsClient.index(Readable.from(unserialized));
return apmApiClient
.readUser({
@@ -271,7 +271,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
fn: () => generateTrace(),
});
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
const { rootNodes: filteredRootNodes } = await fetchAndBuildCriticalPathTree({
fn: () => generateTrace(),
@@ -411,7 +411,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
]);
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
const { rootNodes: filteredRootNodes } = await fetchAndBuildCriticalPathTree({
fn: () => generateTrace(),
@@ -428,6 +428,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
});
}
diff --git a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts
index 653672879c2813..369490ae06d446 100644
--- a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts
@@ -17,7 +17,7 @@ import { generateTrace } from './generate_trace';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2022-01-01T00:00:00.000Z').getTime();
const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1;
@@ -103,7 +103,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: 'python', environment: 'production', agentName: 'python' })
.instance('python');
- return synthtraceEsClient.index(
+ return apmSynthtraceEsClient.index(
timerange(start, end)
.interval('15m')
.rate(1)
@@ -220,6 +220,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
});
}
diff --git a/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts b/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts
index 3cd580a6e7a968..a6d55c7d02bce7 100644
--- a/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts
@@ -16,14 +16,14 @@ export function generateLargeTrace({
start,
end,
rootTransactionName,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
repeaterFactor,
environment,
}: {
start: number;
end: number;
rootTransactionName: string;
- synthtraceEsClient: ApmSynthtraceEsClient;
+ apmSynthtraceEsClient: ApmSynthtraceEsClient;
repeaterFactor: number;
environment: string;
}) {
@@ -137,5 +137,5 @@ export function generateLargeTrace({
}).getTransaction();
});
- return synthtraceEsClient.index(traces);
+ return apmSynthtraceEsClient.index(traces);
}
diff --git a/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts b/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts
index d16a9efd082ed8..023db5c0d2ba0f 100644
--- a/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts
@@ -26,7 +26,7 @@ const environment = 'long_trace_scenario';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const es = getService('es');
// FLAKY: https://github.com/elastic/kibana/issues/177660
@@ -37,14 +37,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
start,
end,
rootTransactionName,
- synthtraceEsClient,
+ apmSynthtraceEsClient,
repeaterFactor: 10,
environment,
});
});
after(async () => {
- await synthtraceEsClient.clean();
+ await apmSynthtraceEsClient.clean();
});
describe('when maxTraceItems is 5000 (default)', () => {
diff --git a/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts b/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts
index 2598a6cdf97abf..a428ea9cb2e508 100644
--- a/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts
@@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2022-01-01T00:00:00.000Z').getTime();
const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1;
@@ -100,10 +100,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
parentTransactionId = span?.['parent.id']!;
traceId = span?.['trace.id']!;
- await synthtraceEsClient.index(Readable.from(unserialized));
+ await apmSynthtraceEsClient.index(Readable.from(unserialized));
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('span details', () => {
let spanDetails: Awaited>['body'];
diff --git a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts
index 8bcf83a503022e..de07f3664104cf 100644
--- a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts
@@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2022-01-01T00:00:00.000Z').getTime();
const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1;
@@ -90,10 +90,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
entryTransactionId = serialized[0]['transaction.id']!;
serviceATraceId = serialized[0]['trace.id']!;
- await synthtraceEsClient.index(Readable.from(unserialized));
+ await apmSynthtraceEsClient.index(Readable.from(unserialized));
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('return trace', () => {
let traces: APIReturnType<'GET /internal/apm/traces/{traceId}'>;
diff --git a/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts b/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts
index 674f56c79a6079..3665bfd8e8ea60 100644
--- a/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts
@@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const start = new Date('2022-01-01T00:00:00.000Z').getTime();
const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1;
@@ -96,10 +96,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
transactionId = transaction?.['transaction.id']!;
traceId = transaction?.['trace.id']!;
- await synthtraceEsClient.index(Readable.from(unserialized));
+ await apmSynthtraceEsClient.index(Readable.from(unserialized));
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('transaction details', () => {
let transactionDetails: Awaited>['body'];
diff --git a/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts
index 123bb0d6d594d8..724390fdfa61fc 100644
--- a/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts
@@ -24,7 +24,7 @@ type ErrorRate =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
// url parameters
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -142,10 +142,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.failure()
),
];
- await synthtraceEsClient.index(documents);
+ await apmSynthtraceEsClient.index(documents);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('returns the transaction error rate', () => {
let errorRateResponse: ErrorRate;
diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts
index eb876e6e312b76..eefe5cfb0d0fe6 100644
--- a/x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts
@@ -25,7 +25,7 @@ type LatencyChartReturnType =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -90,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'development', agentName: 'go' })
.instance('instance-b');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.ratePerMinute(GO_PROD_RATE)
.generator((timestamp) =>
@@ -110,7 +110,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
const expectedLatencyAvgValueMs =
((GO_PROD_RATE * GO_PROD_DURATION + GO_DEV_RATE * GO_DEV_DURATION) /
diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts
index d8868f59aa7eb7..7468437d8bc725 100644
--- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts
@@ -25,7 +25,7 @@ type TransactionsGroupsMainStatistics =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const supertest = getService('supertest');
const es = getService('es');
const serviceName = 'synth-go';
@@ -107,7 +107,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(1)
@@ -135,7 +135,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('Transaction groups with avg transaction duration alerts', () => {
let ruleId: string;
diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts
index e195e035d50a27..77a4b67b4bc4e1 100644
--- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts
@@ -21,7 +21,7 @@ type TransactionsGroupsDetailedStatistics =
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -94,7 +94,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const transactionName = 'GET /api/product/list';
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -118,7 +118,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
describe('without comparisons', () => {
let transactionsStatistics: TransactionsGroupsDetailedStatistics;
diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts
index 646e1b790add03..d7c5e78fdcd124 100644
--- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts
@@ -16,7 +16,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
- const synthtraceEsClient = getService('synthtraceEsClient');
+ const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const serviceName = 'synth-go';
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
@@ -97,7 +97,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.service({ name: serviceName, environment: 'production', agentName: 'go' })
.instance('instance-a');
- await synthtraceEsClient.index([
+ await apmSynthtraceEsClient.index([
timerange(start, end)
.interval('1m')
.rate(GO_PROD_RATE)
@@ -124,7 +124,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
}),
]);
});
- after(() => synthtraceEsClient.clean());
+ after(() => apmSynthtraceEsClient.clean());
it('returns the correct data', async () => {
const transactionsGroupsPrimaryStatistics = await callApi();
diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/public_complete/public_complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/public_complete/public_complete.spec.ts
index 4430d0405764db..ac2fa36f6b0fd1 100644
--- a/x-pack/test/observability_ai_assistant_api_integration/tests/public_complete/public_complete.spec.ts
+++ b/x-pack/test/observability_ai_assistant_api_integration/tests/public_complete/public_complete.spec.ts
@@ -44,12 +44,19 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let proxy: LlmProxy;
let connectorId: string;
- async function getEvents(
- params: {
- actions?: Array>;
- instructions?: string[];
- },
- cb: (conversationSimulator: LlmResponseSimulator) => Promise
+ interface RequestOptions {
+ actions?: Array>;
+ instructions?: string[];
+ format?: 'openai';
+ }
+
+ type ConversationSimulatorCallback = (
+ conversationSimulator: LlmResponseSimulator
+ ) => Promise;
+
+ async function getResponseBody(
+ { actions, instructions, format }: RequestOptions,
+ conversationSimulatorCallback: ConversationSimulatorCallback
) {
const titleInterceptor = proxy.intercept('title', (body) => isFunctionTitleRequest(body));
@@ -61,13 +68,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const responsePromise = new Promise((resolve, reject) => {
supertest
.post(PUBLIC_COMPLETE_API_URL)
+ .query({
+ format,
+ })
.set('kbn-xsrf', 'foo')
.send({
messages,
connectorId,
persist: true,
- actions: params.actions,
- instructions: params.instructions,
+ actions,
+ instructions,
})
.end((err, response) => {
if (err) {
@@ -87,11 +97,22 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await titleSimulator.complete();
await conversationSimulator.status(200);
- await cb(conversationSimulator);
+ if (conversationSimulatorCallback) {
+ await conversationSimulatorCallback(conversationSimulator);
+ }
const response = await responsePromise;
- return String(response.body)
+ return String(response.body);
+ }
+
+ async function getEvents(
+ options: RequestOptions,
+ conversationSimulatorCallback: ConversationSimulatorCallback
+ ) {
+ const responseBody = await getResponseBody(options, conversationSimulatorCallback);
+
+ return responseBody
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
@@ -99,6 +120,17 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.slice(2); // ignore context request/response, we're testing this elsewhere
}
+ async function getOpenAIResponse(conversationSimulatorCallback: ConversationSimulatorCallback) {
+ const responseBody = await getResponseBody(
+ {
+ format: 'openai',
+ },
+ conversationSimulatorCallback
+ );
+
+ return responseBody;
+ }
+
before(async () => {
proxy = await createLlmProxy(log);
@@ -209,6 +241,72 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(request.messages[0].content).to.contain('This is a random instruction');
});
});
+
+ describe('with openai format', async () => {
+ let responseBody: string;
+
+ before(async () => {
+ responseBody = await getOpenAIResponse(async (conversationSimulator) => {
+ await conversationSimulator.next('Hello');
+ await conversationSimulator.complete();
+ });
+ });
+
+ function extractDataParts(lines: string[]) {
+ return lines.map((line) => {
+ // .replace is easier, but we want to verify here whether
+ // it matches the SSE syntax (`data: ...`)
+ const [, dataPart] = line.match(/^data: (.*)$/) || ['', ''];
+ return dataPart.trim();
+ });
+ }
+
+ function getLines() {
+ return responseBody.split('\n\n').filter(Boolean);
+ }
+
+ it('outputs each line an SSE-compatible format (data: ...)', () => {
+ const lines = getLines();
+
+ lines.forEach((line) => {
+ expect(line.match(/^data: /));
+ });
+ });
+
+ it('ouputs one chunk, and one [DONE] event', () => {
+ const dataParts = extractDataParts(getLines());
+
+ expect(dataParts[0]).not.to.be.empty();
+ expect(dataParts[1]).to.be('[DONE]');
+ });
+
+ it('outuputs an OpenAI-compatible chunk', () => {
+ const [dataLine] = extractDataParts(getLines());
+
+ expect(() => {
+ JSON.parse(dataLine);
+ }).not.to.throwException();
+
+ const parsedChunk = JSON.parse(dataLine);
+
+ expect(parsedChunk).to.eql({
+ model: 'unknown',
+ choices: [
+ {
+ delta: {
+ content: 'Hello',
+ },
+ finish_reason: null,
+ index: 0,
+ },
+ ],
+ object: 'chat.completion.chunk',
+ // just test that these are a string and a number
+ id: String(parsedChunk.id),
+ created: Number(parsedChunk.created),
+ });
+ });
+ });
});
}
diff --git a/x-pack/test/observability_api_integration/common/bootstrap_synthtrace.ts b/x-pack/test/observability_api_integration/common/bootstrap_synthtrace.ts
new file mode 100644
index 00000000000000..fd3997e7630cf2
--- /dev/null
+++ b/x-pack/test/observability_api_integration/common/bootstrap_synthtrace.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 {
+ ApmSynthtraceEsClient,
+ ApmSynthtraceKibanaClient,
+ createLogger,
+ LogLevel,
+} from '@kbn/apm-synthtrace';
+import url from 'url';
+import { kbnTestConfig } from '@kbn/test';
+import { FtrProviderContext } from './ftr_provider_context';
+
+export async function bootstrapApmSynthtraceEsClient(
+ context: FtrProviderContext,
+ kibanaClient: ApmSynthtraceKibanaClient
+) {
+ const es = context.getService('es');
+
+ const kibanaVersion = await kibanaClient.fetchLatestApmPackageVersion();
+ await kibanaClient.installApmPackage(kibanaVersion);
+
+ const esClient = new ApmSynthtraceEsClient({
+ client: es,
+ logger: createLogger(LogLevel.info),
+ version: kibanaVersion,
+ refreshAfterIndex: true,
+ });
+
+ return esClient;
+}
+
+export function getSynthtraceKibanaClient(kibanaServerUrl: string) {
+ const kibanaServerUrlWithAuth = url
+ .format({
+ ...url.parse(kibanaServerUrl),
+ auth: `elastic:${kbnTestConfig.getUrlParts().password}`,
+ })
+ .slice(0, -1);
+
+ const kibanaClient = new ApmSynthtraceKibanaClient({
+ target: kibanaServerUrlWithAuth,
+ logger: createLogger(LogLevel.debug),
+ });
+
+ return kibanaClient;
+}
diff --git a/x-pack/test/observability_api_integration/common/config.ts b/x-pack/test/observability_api_integration/common/config.ts
index 83249182084f3e..8baf4f5d116f09 100644
--- a/x-pack/test/observability_api_integration/common/config.ts
+++ b/x-pack/test/observability_api_integration/common/config.ts
@@ -5,7 +5,13 @@
* 2.0.
*/
-import { FtrConfigProviderContext } from '@kbn/test';
+import { Config, FtrConfigProviderContext, kbnTestConfig } from '@kbn/test';
+import { format, UrlObject } from 'url';
+import { LogsSynthtraceEsClient, createLogger, LogLevel } from '@kbn/apm-synthtrace';
+import supertest from 'supertest';
+import { bootstrapApmSynthtraceEsClient, getSynthtraceKibanaClient } from './bootstrap_synthtrace';
+import { FtrProviderContext } from './ftr_provider_context';
+import { createObsApiClient } from './obs_api_supertest';
interface Settings {
license: 'basic' | 'trial';
@@ -13,6 +19,41 @@ interface Settings {
name: string;
}
+export type CustomApiTestServices = ReturnType;
+function getCustomApiTestServices(xPackAPITestsConfig: Config) {
+ const servers = xPackAPITestsConfig.get('servers');
+ const kibanaServer = servers.kibana as UrlObject;
+ const kibanaServerUrl = format(kibanaServer);
+ const synthtraceKibanaClient = getSynthtraceKibanaClient(kibanaServerUrl);
+
+ return {
+ apmSynthtraceEsClient: (context: FtrProviderContext) => {
+ return bootstrapApmSynthtraceEsClient(context, synthtraceKibanaClient);
+ },
+ logSynthtraceEsClient: (context: FtrProviderContext) =>
+ new LogsSynthtraceEsClient({
+ client: context.getService('es'),
+ logger: createLogger(LogLevel.info),
+ refreshAfterIndex: true,
+ }),
+ synthtraceKibanaClient: () => synthtraceKibanaClient,
+ obsApiClient: async (context: FtrProviderContext) => {
+ const getApiClientForUsername = (username: string) => {
+ const url = format({
+ ...kibanaServer,
+ auth: `${username}:${kbnTestConfig.getUrlParts().password}`,
+ });
+
+ return createObsApiClient(supertest(url));
+ };
+
+ return {
+ adminUser: getApiClientForUsername('elastic'),
+ };
+ },
+ };
+}
+
export function createTestConfig(settings: Settings) {
const { testFiles, license, name } = settings;
@@ -21,10 +62,15 @@ export function createTestConfig(settings: Settings) {
require.resolve('../../api_integration/config.ts')
);
+ const customTestServices = getCustomApiTestServices(xPackAPITestsConfig);
+
return {
testFiles,
servers: xPackAPITestsConfig.get('servers'),
- services: xPackAPITestsConfig.get('services'),
+ services: {
+ ...xPackAPITestsConfig.get('services'),
+ ...customTestServices,
+ },
junit: {
reportName: name,
},
diff --git a/x-pack/test/observability_api_integration/common/ftr_provider_context.ts b/x-pack/test/observability_api_integration/common/ftr_provider_context.ts
index 2ea45b854eb280..b1d69d89e287a5 100644
--- a/x-pack/test/observability_api_integration/common/ftr_provider_context.ts
+++ b/x-pack/test/observability_api_integration/common/ftr_provider_context.ts
@@ -5,4 +5,12 @@
* 2.0.
*/
+import { GenericFtrProviderContext } from '@kbn/test';
+import { services } from '../../api_integration/services';
+import { CustomApiTestServices } from './config';
+
export type { FtrProviderContext } from '../../api_integration/ftr_provider_context';
+export type ObsFtrProviderContext = GenericFtrProviderContext<
+ typeof services & CustomApiTestServices,
+ {}
+>;
diff --git a/x-pack/test/observability_api_integration/common/obs_api_supertest.ts b/x-pack/test/observability_api_integration/common/obs_api_supertest.ts
new file mode 100644
index 00000000000000..e0788dcd6785d7
--- /dev/null
+++ b/x-pack/test/observability_api_integration/common/obs_api_supertest.ts
@@ -0,0 +1,111 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { format } from 'url';
+import supertest from 'supertest';
+import request from 'superagent';
+import { formatRequest, ClientRequestParamsOf, ReturnOf } from '@kbn/server-route-repository';
+import type {
+ ObservabilityServerRouteRepository,
+ APIEndpoint,
+} from '@kbn/observability-plugin/server';
+
+export type APIReturnType = ReturnOf<
+ ObservabilityServerRouteRepository,
+ TEndpoint
+>;
+
+export type APIClientRequestParamsOf = ClientRequestParamsOf<
+ ObservabilityServerRouteRepository,
+ TEndpoint
+>;
+
+export function createObsApiClient(st: supertest.SuperTest) {
+ return async (
+ options: {
+ type?: 'form-data';
+ endpoint: TEndpoint;
+ spaceId?: string;
+ } & APIClientRequestParamsOf & { params?: { query?: { _inspect?: boolean } } }
+ ): Promise> => {
+ const { endpoint, type } = options;
+
+ const params = 'params' in options ? (options.params as Record) : {};
+
+ const { method, pathname, version } = formatRequest(endpoint, params.path);
+ const pathnameWithSpaceId = options.spaceId ? `/s/${options.spaceId}${pathname}` : pathname;
+ const url = format({ pathname: pathnameWithSpaceId, query: params?.query });
+
+ // eslint-disable-next-line no-console
+ console.debug(`Calling Observability API: ${method.toUpperCase()} ${url}`);
+
+ const headers: Record = {
+ 'kbn-xsrf': 'foo',
+ 'x-elastic-internal-origin': 'foo',
+ };
+
+ if (version) {
+ headers['Elastic-Api-Version'] = version;
+ }
+
+ let res: request.Response;
+ if (type === 'form-data') {
+ const fields: Array<[string, any]> = Object.entries(params.body);
+ const formDataRequest = st[method](url)
+ .set(headers)
+ .set('Content-type', 'multipart/form-data');
+
+ for (const field of fields) {
+ formDataRequest.field(field[0], field[1]);
+ }
+
+ res = await formDataRequest;
+ } else if (params.body) {
+ res = await st[method](url).send(params.body).set(headers);
+ } else {
+ res = await st[method](url).set(headers);
+ }
+
+ // supertest doesn't throw on http errors
+ if (res?.status !== 200) {
+ throw new ObservabilityApiError(res, endpoint);
+ }
+
+ return res;
+ };
+}
+
+type ApiErrorResponse = Omit & {
+ body: {
+ statusCode: number;
+ error: string;
+ message: string;
+ attributes: object;
+ };
+};
+
+export type ObservabilityApiSupertest = ReturnType;
+
+export class ObservabilityApiError extends Error {
+ res: ApiErrorResponse;
+
+ constructor(res: request.Response, endpoint: string) {
+ super(
+ `Unhandled ObservabilityApiError.
+Status: "${res.status}"
+Endpoint: "${endpoint}"
+Body: ${JSON.stringify(res.body)}`
+ );
+
+ this.res = res;
+ }
+}
+
+export interface SupertestReturnType {
+ status: number;
+ body: APIReturnType;
+}
diff --git a/x-pack/test/observability_api_integration/trial/tests/index.ts b/x-pack/test/observability_api_integration/trial/tests/index.ts
index e426efd90188ce..3d7f31517121d9 100644
--- a/x-pack/test/observability_api_integration/trial/tests/index.ts
+++ b/x-pack/test/observability_api_integration/trial/tests/index.ts
@@ -11,5 +11,6 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) {
describe('Observability specs (trial)', function () {
loadTestFile(require.resolve('./annotations'));
+ loadTestFile(require.resolve('./obs_alert_details_context'));
});
}
diff --git a/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts b/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts
new file mode 100644
index 00000000000000..bf75c8c5585d47
--- /dev/null
+++ b/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts
@@ -0,0 +1,515 @@
+/*
+ * 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 moment from 'moment';
+import { log, apm, generateShortId, timerange } from '@kbn/apm-synthtrace-client';
+import expect from '@kbn/expect';
+import { LogCategory } from '@kbn/apm-plugin/server/routes/assistant_functions/get_log_categories';
+import { SupertestReturnType } from '../../common/obs_api_supertest';
+import { ObsFtrProviderContext } from '../../common/ftr_provider_context';
+
+// eslint-disable-next-line import/no-default-export
+export default function ApiTest({ getService }: ObsFtrProviderContext) {
+ const obsApiClient = getService('obsApiClient');
+ const apmSynthtraceClient = getService('apmSynthtraceEsClient');
+ const logSynthtraceClient = getService('logSynthtraceEsClient');
+
+ describe('fetching observability alerts details context for AI assistant contextual insights', () => {
+ const start = moment().subtract(10, 'minutes').valueOf();
+ const end = moment().valueOf();
+ const range = timerange(start, end);
+
+ describe('when no traces or logs are available', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ },
+ },
+ });
+ });
+
+ it('returns nothing', () => {
+ expect(response.body.alertContext).to.eql([]);
+ });
+ });
+
+ describe('when traces and logs are ingested and logs are not annotated with service.name', async () => {
+ before(async () => {
+ await Promise.all([
+ ingestTraces({ 'service.name': 'Backend', 'container.id': 'my-container-a' }),
+ ingestLogs({
+ 'container.id': 'my-container-a',
+ 'kubernetes.pod.name': 'pod-a',
+ }),
+ ]);
+ });
+
+ after(async () => {
+ await cleanup();
+ });
+
+ describe('when no params are specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ },
+ },
+ });
+ });
+
+ it('returns only 1 log category', async () => {
+ expect(response.body.alertContext).to.have.length(1);
+
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+
+ expect(
+ logCategories.map(({ errorCategory }: { errorCategory: string }) => errorCategory)
+ ).to.eql(['Error message from container my-container-a']);
+ });
+ });
+
+ describe('when service name is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'service.name': 'Backend',
+ },
+ },
+ });
+ });
+
+ it('returns service summary', () => {
+ const serviceSummary = response.body.alertContext.find(
+ ({ key }) => key === 'serviceSummary'
+ );
+ expect(serviceSummary?.data).to.eql({
+ 'service.name': 'Backend',
+ 'service.environment': ['production'],
+ 'agent.name': 'java',
+ 'service.version': ['1.0.0'],
+ 'language.name': 'java',
+ instances: 1,
+ anomalies: [],
+ alerts: [],
+ deployments: [],
+ });
+ });
+
+ it('returns downstream dependencies', async () => {
+ const downstreamDependencies = response.body.alertContext.find(
+ ({ key }) => key === 'downstreamDependencies'
+ );
+ expect(downstreamDependencies?.data).to.eql([
+ {
+ 'span.destination.service.resource': 'elasticsearch',
+ 'span.type': 'db',
+ 'span.subtype': 'elasticsearch',
+ },
+ ]);
+ });
+
+ it('returns log categories', () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+
+ expect(logCategories).to.have.length(1);
+
+ const logCategory = logCategories[0];
+ expect(logCategory?.sampleMessage).to.match(
+ /Error message #\d{16} from container my-container-a/
+ );
+ expect(logCategory?.docCount).to.be.greaterThan(0);
+ expect(logCategory?.errorCategory).to.be('Error message from container my-container-a');
+ });
+ });
+
+ describe('when container id is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'container.id': 'my-container-a',
+ },
+ },
+ });
+ });
+
+ it('returns service summary', () => {
+ const serviceSummary = response.body.alertContext.find(
+ ({ key }) => key === 'serviceSummary'
+ );
+ expect(serviceSummary?.data).to.eql({
+ 'service.name': 'Backend',
+ 'service.environment': ['production'],
+ 'agent.name': 'java',
+ 'service.version': ['1.0.0'],
+ 'language.name': 'java',
+ instances: 1,
+ anomalies: [],
+ alerts: [],
+ deployments: [],
+ });
+ });
+
+ it('returns downstream dependencies', async () => {
+ const downstreamDependencies = response.body.alertContext.find(
+ ({ key }) => key === 'downstreamDependencies'
+ );
+ expect(downstreamDependencies?.data).to.eql([
+ {
+ 'span.destination.service.resource': 'elasticsearch',
+ 'span.type': 'db',
+ 'span.subtype': 'elasticsearch',
+ },
+ ]);
+ });
+
+ it('returns log categories', () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+ expect(logCategories).to.have.length(1);
+
+ const logCategory = logCategories[0];
+ expect(logCategory?.sampleMessage).to.match(
+ /Error message #\d{16} from container my-container-a/
+ );
+ expect(logCategory?.docCount).to.be.greaterThan(0);
+ expect(logCategory?.errorCategory).to.be('Error message from container my-container-a');
+ });
+ });
+
+ describe('when non-existing container id is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'container.id': 'non-existing-container',
+ },
+ },
+ });
+ });
+
+ it('returns nothing', () => {
+ expect(response.body.alertContext).to.eql([]);
+ });
+ });
+
+ describe('when non-existing service.name is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'service.name': 'non-existing-service',
+ },
+ },
+ });
+ });
+
+ it('returns empty service summary', () => {
+ const serviceSummary = response.body.alertContext.find(
+ ({ key }) => key === 'serviceSummary'
+ );
+ expect(serviceSummary?.data).to.eql({
+ 'service.name': 'non-existing-service',
+ 'service.environment': [],
+ instances: 1,
+ anomalies: [],
+ alerts: [],
+ deployments: [],
+ });
+ });
+
+ it('returns no downstream dependencies', async () => {
+ const downstreamDependencies = response.body.alertContext.find(
+ ({ key }) => key === 'downstreamDependencies'
+ );
+ expect(downstreamDependencies).to.eql(undefined);
+ });
+
+ it('returns log categories', () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+ expect(logCategories).to.have.length(1);
+ });
+ });
+ });
+
+ describe('when traces and logs are ingested and logs are annotated with service.name', async () => {
+ before(async () => {
+ await ingestTraces({ 'service.name': 'Backend', 'container.id': 'my-container-a' });
+ await ingestLogs({
+ 'service.name': 'Backend',
+ 'container.id': 'my-container-a',
+ 'kubernetes.pod.name': 'pod-a',
+ });
+
+ // also ingest unrelated Frontend traces and logs that should not show up in the response when fetching "Backend"-related things
+ await ingestTraces({ 'service.name': 'Frontend', 'container.id': 'my-container-b' });
+ await ingestLogs({
+ 'service.name': 'Frontend',
+ 'container.id': 'my-container-b',
+ 'kubernetes.pod.name': 'pod-b',
+ });
+
+ // also ingest logs that are not annotated with service.name
+ await ingestLogs({
+ 'container.id': 'my-container-c',
+ 'kubernetes.pod.name': 'pod-c',
+ });
+ });
+
+ after(async () => {
+ await cleanup();
+ });
+
+ describe('when no params are specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ },
+ },
+ });
+ });
+
+ it('returns no service summary', async () => {
+ const serviceSummary = response.body.alertContext.find(
+ ({ key }) => key === 'serviceSummary'
+ );
+ expect(serviceSummary).to.be(undefined);
+ });
+
+ it('returns 1 log category', async () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+ expect(
+ logCategories.map(({ errorCategory }: { errorCategory: string }) => errorCategory)
+ ).to.eql(['Error message from service', 'Error message from container my-container-c']);
+ });
+ });
+
+ describe('when service name is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'service.name': 'Backend',
+ },
+ },
+ });
+ });
+
+ it('returns log categories', () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+ expect(logCategories).to.have.length(1);
+
+ const logCategory = logCategories[0];
+ expect(logCategory?.sampleMessage).to.match(/Error message #\d{16} from service Backend/);
+ expect(logCategory?.docCount).to.be.greaterThan(0);
+ expect(logCategory?.errorCategory).to.be('Error message from service Backend');
+ });
+ });
+
+ describe('when container id is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'container.id': 'my-container-a',
+ },
+ },
+ });
+ });
+
+ it('returns log categories', () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+ expect(logCategories).to.have.length(1);
+
+ const logCategory = logCategories[0];
+ expect(logCategory?.sampleMessage).to.match(/Error message #\d{16} from service Backend/);
+ expect(logCategory?.docCount).to.be.greaterThan(0);
+ expect(logCategory?.errorCategory).to.be('Error message from service Backend');
+ });
+ });
+
+ describe('when non-existing service.name is specified', async () => {
+ let response: SupertestReturnType<'GET /internal/observability/assistant/alert_details_contextual_insights'>;
+ before(async () => {
+ response = await obsApiClient.adminUser({
+ endpoint: 'GET /internal/observability/assistant/alert_details_contextual_insights',
+ params: {
+ query: {
+ alert_started_at: new Date(end).toISOString(),
+ 'service.name': 'non-existing-service',
+ },
+ },
+ });
+ });
+
+ it('returns empty service summary', () => {
+ const serviceSummary = response.body.alertContext.find(
+ ({ key }) => key === 'serviceSummary'
+ );
+ expect(serviceSummary?.data).to.eql({
+ 'service.name': 'non-existing-service',
+ 'service.environment': [],
+ instances: 1,
+ anomalies: [],
+ alerts: [],
+ deployments: [],
+ });
+ });
+
+ it('does not return log categories', () => {
+ const logCategories = response.body.alertContext.find(
+ ({ key }) => key === 'logCategories'
+ )?.data as LogCategory[];
+ expect(logCategories).to.have.length(1);
+
+ expect(
+ logCategories.map(({ errorCategory }: { errorCategory: string }) => errorCategory)
+ ).to.eql(['Error message from container my-container-c']);
+ });
+ });
+ });
+
+ function ingestTraces(eventMetadata: {
+ 'service.name': string;
+ 'container.id'?: string;
+ 'host.name'?: string;
+ 'kubernetes.pod.name'?: string;
+ }) {
+ const serviceInstance = apm
+ .service({
+ name: eventMetadata['service.name'],
+ environment: 'production',
+ agentName: 'java',
+ })
+ .instance('my-instance');
+
+ const events = range
+ .interval('1m')
+ .rate(1)
+ .generator((timestamp) => {
+ return serviceInstance
+ .transaction({ transactionName: 'tx' })
+ .timestamp(timestamp)
+ .duration(10000)
+ .defaults({ 'service.version': '1.0.0', ...eventMetadata })
+ .outcome('success')
+ .children(
+ serviceInstance
+ .span({
+ spanName: 'GET apm-*/_search',
+ spanType: 'db',
+ spanSubtype: 'elasticsearch',
+ })
+ .duration(1000)
+ .success()
+ .destination('elasticsearch')
+ .timestamp(timestamp)
+ );
+ });
+
+ return apmSynthtraceClient.index(events);
+ }
+
+ function ingestLogs(eventMetadata: {
+ 'service.name'?: string;
+ 'container.id'?: string;
+ 'kubernetes.pod.name'?: string;
+ 'host.name'?: string;
+ }) {
+ const getMessage = () => {
+ const msgPrefix = `Error message #${generateShortId()}`;
+
+ if (eventMetadata['service.name']) {
+ return `${msgPrefix} from service ${eventMetadata['service.name']}`;
+ }
+
+ if (eventMetadata['container.id']) {
+ return `${msgPrefix} from container ${eventMetadata['container.id']}`;
+ }
+
+ if (eventMetadata['kubernetes.pod.name']) {
+ return `${msgPrefix} from pod ${eventMetadata['kubernetes.pod.name']}`;
+ }
+
+ if (eventMetadata['host.name']) {
+ return `${msgPrefix} from host ${eventMetadata['host.name']}`;
+ }
+
+ return msgPrefix;
+ };
+
+ const events = range
+ .interval('1m')
+ .rate(1)
+ .generator((timestamp) => {
+ return [
+ log
+ .create()
+ .message(getMessage())
+ .logLevel('error')
+ .defaults({
+ 'trace.id': generateShortId(),
+ 'agent.name': 'synth-agent',
+ ...eventMetadata,
+ })
+ .timestamp(timestamp),
+ ];
+ });
+
+ return logSynthtraceClient.index(events);
+ }
+
+ async function cleanup() {
+ await apmSynthtraceClient.clean();
+ await logSynthtraceClient.clean();
+ }
+ });
+}
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts
index 8b59070202b087..fb7543b9fe700e 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import {
DETECTION_ENGINE_RULES_BULK_ACTION,
DETECTION_ENGINE_RULES_URL,
@@ -19,9 +19,11 @@ import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/com
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import { WebhookAuthType } from '@kbn/stack-connectors-plugin/common/webhook/constants';
+import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine';
import {
binaryToString,
getSimpleMlRule,
+ getCustomQueryRuleParams,
getSimpleRule,
getSimpleRuleOutput,
getSlackAction,
@@ -42,6 +44,7 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
+ const securitySolutionApi = getService('securitySolutionApi');
const es = getService('es');
const log = getService('log');
const esArchiver = getService('esArchiver');
@@ -98,7 +101,9 @@ export default ({ getService }: FtrProviderContext): void => {
});
it('should export rules', async () => {
- await createRule(supertest, log, getSimpleRule());
+ const mockRule = getCustomQueryRuleParams();
+
+ await securitySolutionApi.createRule({ body: mockRule });
const { body } = await postBulkAction()
.send({ query: '', action: BulkActionTypeEnum.export })
@@ -109,12 +114,8 @@ export default ({ getService }: FtrProviderContext): void => {
const [ruleJson, exportDetailsJson] = body.toString().split(/\n/);
- const rule = removeServerGeneratedProperties(JSON.parse(ruleJson));
- const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME);
- expect(rule).to.eql(expectedRule);
-
- const exportDetails = JSON.parse(exportDetailsJson);
- expect(exportDetails).to.eql({
+ expect(JSON.parse(ruleJson)).toMatchObject(mockRule);
+ expect(JSON.parse(exportDetailsJson)).toEqual({
exported_exception_list_count: 0,
exported_exception_list_item_count: 0,
exported_count: 1,
@@ -132,6 +133,35 @@ export default ({ getService }: FtrProviderContext): void => {
missing_action_connections: [],
});
});
+
+ it('should export rules with defaultbale fields when values are set', async () => {
+ const defaultableFields: BaseDefaultableFields = {
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ };
+ const mockRule = getCustomQueryRuleParams(defaultableFields);
+
+ await securitySolutionApi.createRule({ body: mockRule });
+
+ const { body } = await securitySolutionApi
+ .performBulkAction({
+ query: {},
+ body: {
+ action: BulkActionTypeEnum.export,
+ },
+ })
+ .expect(200)
+ .expect('Content-Type', 'application/ndjson')
+ .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"')
+ .parse(binaryToString);
+
+ const [ruleJson] = body.toString().split(/\n/);
+
+ expect(JSON.parse(ruleJson)).toMatchObject(defaultableFields);
+ });
+
it('should export rules with actions connectors', async () => {
// create new actions
const webHookAction = await createWebHookConnector();
@@ -184,7 +214,7 @@ export default ({ getService }: FtrProviderContext): void => {
const rule = removeServerGeneratedProperties(JSON.parse(ruleJson));
const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME);
- expect(rule).to.eql({
+ expect(rule).toEqual({
...expectedRule,
actions: [
{
@@ -200,14 +230,14 @@ export default ({ getService }: FtrProviderContext): void => {
],
});
const { attributes, id, type } = JSON.parse(connectorsJson);
- expect(attributes.actionTypeId).to.eql(exportedConnectors.attributes.actionTypeId);
- expect(id).to.eql(exportedConnectors.id);
- expect(type).to.eql(exportedConnectors.type);
- expect(attributes.name).to.eql(exportedConnectors.attributes.name);
- expect(attributes.secrets).to.eql(exportedConnectors.attributes.secrets);
- expect(attributes.isMissingSecrets).to.eql(exportedConnectors.attributes.isMissingSecrets);
+ expect(attributes.actionTypeId).toEqual(exportedConnectors.attributes.actionTypeId);
+ expect(id).toEqual(exportedConnectors.id);
+ expect(type).toEqual(exportedConnectors.type);
+ expect(attributes.name).toEqual(exportedConnectors.attributes.name);
+ expect(attributes.secrets).toEqual(exportedConnectors.attributes.secrets);
+ expect(attributes.isMissingSecrets).toEqual(exportedConnectors.attributes.isMissingSecrets);
const exportDetails = JSON.parse(exportDetailsJson);
- expect(exportDetails).to.eql({
+ expect(exportDetails).toEqual({
exported_exception_list_count: 0,
exported_exception_list_item_count: 0,
exported_count: 2,
@@ -235,10 +265,10 @@ export default ({ getService }: FtrProviderContext): void => {
.send({ query: '', action: BulkActionTypeEnum.delete })
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the deleted rule is returned with the response
- expect(body.attributes.results.deleted[0].name).to.eql(testRule.name);
+ expect(body.attributes.results.deleted[0].name).toEqual(testRule.name);
// Check that the updates have been persisted
await fetchRule(ruleId).expect(404);
@@ -252,14 +282,14 @@ export default ({ getService }: FtrProviderContext): void => {
.send({ query: '', action: BulkActionTypeEnum.enable })
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].enabled).to.eql(true);
+ expect(body.attributes.results.updated[0].enabled).toEqual(true);
// Check that the updates have been persisted
const { body: ruleBody } = await fetchRule(ruleId).expect(200);
- expect(ruleBody.enabled).to.eql(true);
+ expect(ruleBody.enabled).toEqual(true);
});
it('should disable rules', async () => {
@@ -270,20 +300,27 @@ export default ({ getService }: FtrProviderContext): void => {
.send({ query: '', action: BulkActionTypeEnum.disable })
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].enabled).to.eql(false);
+ expect(body.attributes.results.updated[0].enabled).toEqual(false);
// Check that the updates have been persisted
const { body: ruleBody } = await fetchRule(ruleId).expect(200);
- expect(ruleBody.enabled).to.eql(false);
+ expect(ruleBody.enabled).toEqual(false);
});
it('should duplicate rules', async () => {
const ruleId = 'ruleId';
- const ruleToDuplicate = getSimpleRule(ruleId);
- await createRule(supertest, log, ruleToDuplicate);
+ const ruleToDuplicate = getCustomQueryRuleParams({
+ rule_id: ruleId,
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ await securitySolutionApi.createRule({ body: ruleToDuplicate });
const { body } = await postBulkAction()
.send({
@@ -293,19 +330,30 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the duplicated rule is returned with the response
- expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`);
+ expect(body.attributes.results.created[0].name).toEqual(
+ `${ruleToDuplicate.name} [Duplicate]`
+ );
// Check that the updates have been persisted
- const { body: rulesResponse } = await supertest
- .get(`${DETECTION_ENGINE_RULES_URL}/_find`)
- .set('kbn-xsrf', 'true')
- .set('elastic-api-version', '2023-10-31')
+ const { body: rulesResponse } = await securitySolutionApi.findRules({ query: {} });
+
+ expect(rulesResponse.total).toEqual(2);
+
+ const duplicatedRuleId = body.attributes.results.created[0].id;
+ const { body: duplicatedRule } = await securitySolutionApi
+ .readRule({
+ query: { id: duplicatedRuleId },
+ })
.expect(200);
- expect(rulesResponse.total).to.eql(2);
+ expect(duplicatedRule).toMatchObject({
+ ...ruleToDuplicate,
+ name: `${ruleToDuplicate.name} [Duplicate]`,
+ rule_id: expect.any(String),
+ });
});
it('should duplicate rules with exceptions - expired exceptions included', async () => {
@@ -381,15 +429,17 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Item should have been duplicated, even if expired
- expect(foundItems.total).to.eql(1);
+ expect(foundItems.total).toEqual(1);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the duplicated rule is returned with the response
- expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`);
+ expect(body.attributes.results.created[0].name).toEqual(
+ `${ruleToDuplicate.name} [Duplicate]`
+ );
// Check that the exceptions are duplicated
- expect(body.attributes.results.created[0].exceptions_list).to.eql([
+ expect(body.attributes.results.created[0].exceptions_list).toEqual([
{
type: exceptionList.type,
list_id: exceptionList.list_id,
@@ -411,7 +461,7 @@ export default ({ getService }: FtrProviderContext): void => {
.set('elastic-api-version', '2023-10-31')
.expect(200);
- expect(rulesResponse.total).to.eql(2);
+ expect(rulesResponse.total).toEqual(2);
});
it('should duplicate rules with exceptions - expired exceptions excluded', async () => {
@@ -487,15 +537,17 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Item should NOT have been duplicated, since it is expired
- expect(foundItems.total).to.eql(0);
+ expect(foundItems.total).toEqual(0);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the duplicated rule is returned with the response
- expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`);
+ expect(body.attributes.results.created[0].name).toEqual(
+ `${ruleToDuplicate.name} [Duplicate]`
+ );
// Check that the exceptions are duplicted
- expect(body.attributes.results.created[0].exceptions_list).to.eql([
+ expect(body.attributes.results.created[0].exceptions_list).toEqual([
{
type: exceptionList.type,
list_id: exceptionList.list_id,
@@ -517,7 +569,7 @@ export default ({ getService }: FtrProviderContext): void => {
.set('kbn-xsrf', 'true')
.expect(200);
- expect(rulesResponse.total).to.eql(2);
+ expect(rulesResponse.total).toEqual(2);
});
describe('edit action', () => {
@@ -575,7 +627,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -583,12 +635,12 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags);
+ expect(bulkEditResponse.attributes.results.updated[0].tags).toEqual(resultingTags);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.tags).to.eql(resultingTags);
+ expect(updatedRule.tags).toEqual(resultingTags);
});
});
@@ -632,7 +684,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -640,12 +692,12 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags);
+ expect(bulkEditResponse.attributes.results.updated[0].tags).toEqual(resultingTags);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.tags).to.eql(resultingTags);
+ expect(updatedRule.tags).toEqual(resultingTags);
});
});
@@ -688,7 +740,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -696,12 +748,12 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags);
+ expect(bulkEditResponse.attributes.results.updated[0].tags).toEqual(resultingTags);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.tags).to.eql(resultingTags);
+ expect(updatedRule.tags).toEqual(resultingTags);
});
});
@@ -765,7 +817,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 1,
succeeded: 0,
@@ -773,14 +825,14 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the rules is returned as skipped with expected skip reason
- expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).to.eql(
+ expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).toEqual(
'RULE_NOT_MODIFIED'
);
// Check that the no changes have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.tags).to.eql(resultingTags);
+ expect(updatedRule.tags).toEqual(resultingTags);
});
}
);
@@ -804,7 +856,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -812,12 +864,12 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(['initial-index-*']);
+ expect(bulkEditResponse.attributes.results.updated[0].index).toEqual(['initial-index-*']);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.index).to.eql(['initial-index-*']);
+ expect(updatedRule.index).toEqual(['initial-index-*']);
});
it('should add index patterns to rules', async () => {
@@ -839,7 +891,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -847,14 +899,14 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(
+ expect(bulkEditResponse.attributes.results.updated[0].index).toEqual(
resultingIndexPatterns
);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.index).to.eql(resultingIndexPatterns);
+ expect(updatedRule.index).toEqual(resultingIndexPatterns);
});
it('should delete index patterns from rules', async () => {
@@ -876,7 +928,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -884,14 +936,14 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(
+ expect(bulkEditResponse.attributes.results.updated[0].index).toEqual(
resultingIndexPatterns
);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.index).to.eql(resultingIndexPatterns);
+ expect(updatedRule.index).toEqual(resultingIndexPatterns);
});
it('should return error if index patterns action is applied to machine learning rule', async () => {
@@ -910,8 +962,13 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({
+ failed: 1,
+ skipped: 0,
+ succeeded: 0,
+ total: 1,
+ });
+ expect(body.attributes.errors[0]).toEqual({
message:
"Index patterns can't be added. Machine learning rule doesn't have index patterns property",
status_code: 500,
@@ -943,8 +1000,13 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({
+ failed: 1,
+ skipped: 0,
+ succeeded: 0,
+ total: 1,
+ });
+ expect(body.attributes.errors[0]).toEqual({
message: "Mutated params invalid: Index patterns can't be empty",
status_code: 500,
rules: [
@@ -976,8 +1038,13 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({
+ failed: 1,
+ skipped: 0,
+ succeeded: 0,
+ total: 1,
+ });
+ expect(body.attributes.errors[0]).toEqual({
message: "Mutated params invalid: Index patterns can't be empty",
status_code: 500,
rules: [
@@ -991,7 +1058,7 @@ export default ({ getService }: FtrProviderContext): void => {
// Check that the rule hasn't been updated
const { body: reFetchedRule } = await fetchRule(ruleId).expect(200);
- expect(reFetchedRule.index).to.eql(['simple-index-*']);
+ expect(reFetchedRule.index).toEqual(['simple-index-*']);
});
const skipIndexPatternsUpdateCases = [
@@ -1063,7 +1130,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(bulkEditResponse.attributes.summary).to.eql({
+ expect(bulkEditResponse.attributes.summary).toEqual({
failed: 0,
skipped: 1,
succeeded: 0,
@@ -1071,14 +1138,14 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the rules is returned as skipped with expected skip reason
- expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).to.eql(
+ expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).toEqual(
'RULE_NOT_MODIFIED'
);
// Check that the no changes have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.index).to.eql(resultingIndexPatterns);
+ expect(updatedRule.index).toEqual(resultingIndexPatterns);
});
}
);
@@ -1106,17 +1173,17 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].timeline_id).to.eql(timelineId);
- expect(body.attributes.results.updated[0].timeline_title).to.eql(timelineTitle);
+ expect(body.attributes.results.updated[0].timeline_id).toEqual(timelineId);
+ expect(body.attributes.results.updated[0].timeline_title).toEqual(timelineTitle);
// Check that the updates have been persisted
const { body: rule } = await fetchRule(ruleId).expect(200);
- expect(rule.timeline_id).to.eql(timelineId);
- expect(rule.timeline_title).to.eql(timelineTitle);
+ expect(rule.timeline_id).toEqual(timelineId);
+ expect(rule.timeline_title).toEqual(timelineTitle);
});
it('should correctly remove timeline template', async () => {
@@ -1130,8 +1197,8 @@ export default ({ getService }: FtrProviderContext): void => {
});
// ensure rule has been created with timeline properties
- expect(createdRule.timeline_id).to.be(timelineId);
- expect(createdRule.timeline_title).to.be(timelineTitle);
+ expect(createdRule.timeline_id).toBe(timelineId);
+ expect(createdRule.timeline_title).toBe(timelineTitle);
const { body } = await postBulkAction()
.send({
@@ -1149,17 +1216,17 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].timeline_id).to.be(undefined);
- expect(body.attributes.results.updated[0].timeline_title).to.be(undefined);
+ expect(body.attributes.results.updated[0].timeline_id).toBe(undefined);
+ expect(body.attributes.results.updated[0].timeline_title).toBe(undefined);
// Check that the updates have been persisted
const { body: rule } = await fetchRule(ruleId).expect(200);
- expect(rule.timeline_id).to.be(undefined);
- expect(rule.timeline_title).to.be(undefined);
+ expect(rule.timeline_id).toBe(undefined);
+ expect(rule.timeline_title).toBe(undefined);
});
it('should return error if index patterns action is applied to machine learning rule', async () => {
@@ -1178,8 +1245,8 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
+ expect(body.attributes.errors[0]).toEqual({
message:
"Index patterns can't be added. Machine learning rule doesn't have index patterns property",
status_code: 500,
@@ -1211,8 +1278,8 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
+ expect(body.attributes.errors[0]).toEqual({
message: "Mutated params invalid: Index patterns can't be empty",
status_code: 500,
rules: [
@@ -1240,12 +1307,12 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.results.updated[0].version).to.be(rule.version + 1);
+ expect(body.attributes.results.updated[0].version).toBe(rule.version + 1);
// Check that the updates have been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.version).to.be(rule.version + 1);
+ expect(updatedRule.version).toBe(rule.version + 1);
});
describe('prebuilt rules', () => {
@@ -1301,13 +1368,13 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({
+ expect(body.attributes.summary).toEqual({
failed: 1,
skipped: 0,
succeeded: 0,
total: 1,
});
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.errors[0]).toEqual({
message: "Elastic rule can't be edited",
status_code: 500,
rules: [
@@ -1369,12 +1436,12 @@ export default ({ getService }: FtrProviderContext): void => {
];
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions);
+ expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql(expectedRuleActions);
+ expect(readRule.actions).toEqual(expectedRuleActions);
});
it('should set action correctly to existing non empty actions list', async () => {
@@ -1427,12 +1494,12 @@ export default ({ getService }: FtrProviderContext): void => {
];
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions);
+ expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql(expectedRuleActions);
+ expect(readRule.actions).toEqual(expectedRuleActions);
});
it('should set actions to empty list, actions payload is empty list', async () => {
@@ -1472,12 +1539,12 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].actions).to.eql([]);
+ expect(body.attributes.results.updated[0].actions).toEqual([]);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql([]);
+ expect(readRule.actions).toEqual([]);
});
});
@@ -1521,12 +1588,12 @@ export default ({ getService }: FtrProviderContext): void => {
];
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions);
+ expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql(expectedRuleActions);
+ expect(readRule.actions).toEqual(expectedRuleActions);
});
it('should add action correctly to non empty actions list of the same type', async () => {
@@ -1586,12 +1653,12 @@ export default ({ getService }: FtrProviderContext): void => {
];
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions);
+ expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql(expectedRuleActions);
+ expect(readRule.actions).toEqual(expectedRuleActions);
});
it('should add action correctly to non empty actions list of a different type', async () => {
@@ -1659,12 +1726,12 @@ export default ({ getService }: FtrProviderContext): void => {
];
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions);
+ expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql(expectedRuleActions);
+ expect(readRule.actions).toEqual(expectedRuleActions);
});
it('should not change actions of rule if empty list of actions added', async () => {
@@ -1704,12 +1771,12 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Check that the rule is skipped and was not updated
- expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id);
+ expect(body.attributes.results.skipped[0].id).toEqual(createdRule.id);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.actions).to.eql([
+ expect(readRule.actions).toEqual([
{
...defaultRuleAction,
uuid: createdRule.actions[0].uuid,
@@ -1755,13 +1822,13 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Check that the rule is skipped and was not updated
- expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id);
+ expect(body.attributes.results.skipped[0].id).toEqual(createdRule.id);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
- expect(readRule.throttle).to.eql(undefined);
- expect(readRule.actions).to.eql(createdRule.actions);
+ expect(readRule.throttle).toEqual(undefined);
+ expect(readRule.actions).toEqual(createdRule.actions);
});
});
@@ -1803,7 +1870,7 @@ export default ({ getService }: FtrProviderContext): void => {
const editedRule = body.attributes.results.updated[0];
// Check that the updated rule is returned with the response
- expect(editedRule.actions).to.eql([
+ expect(editedRule.actions).toEqual([
{
...webHookActionMock,
id: webHookConnector.id,
@@ -1813,12 +1880,12 @@ export default ({ getService }: FtrProviderContext): void => {
},
]);
// version of prebuilt rule should not change
- expect(editedRule.version).to.be(prebuiltRule.version);
+ expect(editedRule.version).toBe(prebuiltRule.version);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(prebuiltRule.rule_id).expect(200);
- expect(readRule.actions).to.eql([
+ expect(readRule.actions).toEqual([
{
...webHookActionMock,
id: webHookConnector.id,
@@ -1827,7 +1894,7 @@ export default ({ getService }: FtrProviderContext): void => {
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
},
]);
- expect(prebuiltRule.version).to.be(readRule.version);
+ expect(prebuiltRule.version).toBe(readRule.version);
});
});
@@ -1863,13 +1930,13 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({
+ expect(body.attributes.summary).toEqual({
failed: 1,
skipped: 0,
succeeded: 0,
total: 1,
});
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.errors[0]).toEqual({
message: "Elastic rule can't be edited",
status_code: 500,
rules: [
@@ -1883,9 +1950,9 @@ export default ({ getService }: FtrProviderContext): void => {
// Check that the updates were not made
const { body: readRule } = await fetchRule(prebuiltRule.rule_id).expect(200);
- expect(readRule.actions).to.eql(prebuiltRule.actions);
- expect(readRule.tags).to.eql(prebuiltRule.tags);
- expect(readRule.version).to.be(prebuiltRule.version);
+ expect(readRule.actions).toEqual(prebuiltRule.actions);
+ expect(readRule.tags).toEqual(prebuiltRule.tags);
+ expect(readRule.version).toBe(prebuiltRule.version);
});
});
@@ -1925,12 +1992,12 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Check that the rule is skipped and was not updated
- expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id);
+ expect(body.attributes.results.skipped[0].id).toEqual(createdRule.id);
// Check that the updates have been persisted
const { body: rule } = await fetchRule(ruleId).expect(200);
- expect(rule.throttle).to.eql(undefined);
+ expect(rule.throttle).toEqual(undefined);
});
});
@@ -1987,7 +2054,7 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle);
+ expect(body.attributes.results.updated[0].throttle).toEqual(expectedThrottle);
const expectedActions = body.attributes.results.updated[0].actions.map(
(action: any) => ({
@@ -2004,8 +2071,8 @@ export default ({ getService }: FtrProviderContext): void => {
// Check that the updates have been persisted
const { body: rule } = await fetchRule(ruleId).expect(200);
- expect(rule.throttle).to.eql(expectedThrottle);
- expect(rule.actions).to.eql(expectedActions);
+ expect(rule.throttle).toEqual(expectedThrottle);
+ expect(rule.actions).toEqual(expectedActions);
});
});
});
@@ -2047,7 +2114,7 @@ export default ({ getService }: FtrProviderContext): void => {
// Check whether notifyWhen set correctly
const { body: rule } = await fetchRuleByAlertApi(createdRule.id).expect(200);
- expect(rule.notify_when).to.eql(expected.notifyWhen);
+ expect(rule.notify_when).toEqual(expected.notifyWhen);
});
});
});
@@ -2078,10 +2145,10 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(400);
- expect(body.statusCode).to.eql(400);
- expect(body.error).to.eql('Bad Request');
- expect(body.message).to.contain('edit.0.value.interval: Invalid');
- expect(body.message).to.contain('edit.0.value.lookback: Invalid');
+ expect(body.statusCode).toEqual(400);
+ expect(body.error).toEqual('Bad Request');
+ expect(body.message).toContain('edit.0.value.interval: Invalid');
+ expect(body.message).toContain('edit.0.value.lookback: Invalid');
});
it('should update schedule values in rules with a valid payload', async () => {
@@ -2108,11 +2175,16 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({
+ failed: 0,
+ skipped: 0,
+ succeeded: 1,
+ total: 1,
+ });
- expect(body.attributes.results.updated[0].interval).to.eql(interval);
- expect(body.attributes.results.updated[0].meta).to.eql({ from: `${lookbackMinutes}m` });
- expect(body.attributes.results.updated[0].from).to.eql(
+ expect(body.attributes.results.updated[0].interval).toEqual(interval);
+ expect(body.attributes.results.updated[0].meta).toEqual({ from: `${lookbackMinutes}m` });
+ expect(body.attributes.results.updated[0].from).toEqual(
`now-${(intervalMinutes + lookbackMinutes) * 60}s`
);
});
@@ -2144,7 +2216,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(setIndexBody.attributes.summary).to.eql({
+ expect(setIndexBody.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -2152,13 +2224,13 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']);
- expect(setIndexBody.attributes.results.updated[0].data_view_id).to.eql(undefined);
+ expect(setIndexBody.attributes.results.updated[0].index).toEqual(['initial-index-*']);
+ expect(setIndexBody.attributes.results.updated[0].data_view_id).toEqual(undefined);
// Check that the updates have been persisted
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
- expect(setIndexRule.index).to.eql(['initial-index-*']);
+ expect(setIndexRule.index).toEqual(['initial-index-*']);
});
it('should return skipped rule and NOT add an index pattern to a rule or overwrite the data view when overwrite_data_views is false', async () => {
@@ -2185,25 +2257,25 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(setIndexBody.attributes.summary).to.eql({
+ expect(setIndexBody.attributes.summary).toEqual({
failed: 0,
skipped: 1,
succeeded: 0,
total: 1,
});
- expect(setIndexBody.attributes.errors).to.be(undefined);
+ expect(setIndexBody.attributes.errors).toBe(undefined);
// Check that the skipped rule is returned with the response
- expect(setIndexBody.attributes.results.skipped[0].id).to.eql(simpleRule.id);
- expect(setIndexBody.attributes.results.skipped[0].name).to.eql(simpleRule.name);
- expect(setIndexBody.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED');
+ expect(setIndexBody.attributes.results.skipped[0].id).toEqual(simpleRule.id);
+ expect(setIndexBody.attributes.results.skipped[0].name).toEqual(simpleRule.name);
+ expect(setIndexBody.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED');
// Check that the rule has not been updated
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
- expect(setIndexRule.index).to.eql(undefined);
- expect(setIndexRule.data_view_id).to.eql(dataViewId);
+ expect(setIndexRule.index).toEqual(undefined);
+ expect(setIndexRule.data_view_id).toEqual(dataViewId);
});
it('should set an index pattern to a rule and overwrite the data view when overwrite_data_views is true', async () => {
@@ -2230,7 +2302,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(setIndexBody.attributes.summary).to.eql({
+ expect(setIndexBody.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -2238,14 +2310,14 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']);
- expect(setIndexBody.attributes.results.updated[0].data_view_id).to.eql(undefined);
+ expect(setIndexBody.attributes.results.updated[0].index).toEqual(['initial-index-*']);
+ expect(setIndexBody.attributes.results.updated[0].data_view_id).toEqual(undefined);
// Check that the updates have been persisted
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
- expect(setIndexRule.index).to.eql(['initial-index-*']);
- expect(setIndexRule.data_view_id).to.eql(undefined);
+ expect(setIndexRule.index).toEqual(['initial-index-*']);
+ expect(setIndexRule.data_view_id).toEqual(undefined);
});
it('should return error when set an empty index pattern to a rule and overwrite the data view when overwrite_data_views is true', async () => {
@@ -2271,8 +2343,8 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
+ expect(body.attributes.errors[0]).toEqual({
message: "Mutated params invalid: Index patterns can't be empty",
status_code: 500,
rules: [
@@ -2307,25 +2379,25 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(setIndexBody.attributes.summary).to.eql({
+ expect(setIndexBody.attributes.summary).toEqual({
failed: 0,
skipped: 1,
succeeded: 0,
total: 1,
});
- expect(setIndexBody.attributes.errors).to.be(undefined);
+ expect(setIndexBody.attributes.errors).toBe(undefined);
// Check that the skipped rule is returned with the response
- expect(setIndexBody.attributes.results.skipped[0].id).to.eql(simpleRule.id);
- expect(setIndexBody.attributes.results.skipped[0].name).to.eql(simpleRule.name);
- expect(setIndexBody.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED');
+ expect(setIndexBody.attributes.results.skipped[0].id).toEqual(simpleRule.id);
+ expect(setIndexBody.attributes.results.skipped[0].name).toEqual(simpleRule.name);
+ expect(setIndexBody.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED');
// Check that the rule has not been updated
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
- expect(setIndexRule.index).to.eql(undefined);
- expect(setIndexRule.data_view_id).to.eql(dataViewId);
+ expect(setIndexRule.index).toEqual(undefined);
+ expect(setIndexRule.data_view_id).toEqual(dataViewId);
});
// This rule will now not have a source defined - as has been the behavior of rules since the beginning
@@ -2353,7 +2425,7 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({
+ expect(body.attributes.summary).toEqual({
failed: 0,
skipped: 0,
succeeded: 1,
@@ -2361,14 +2433,14 @@ export default ({ getService }: FtrProviderContext): void => {
});
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].index).to.eql(undefined);
- expect(body.attributes.results.updated[0].data_view_id).to.eql(undefined);
+ expect(body.attributes.results.updated[0].index).toEqual(undefined);
+ expect(body.attributes.results.updated[0].data_view_id).toEqual(undefined);
// Check that the updates have been persisted
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
- expect(setIndexRule.index).to.eql(undefined);
- expect(setIndexRule.data_view_id).to.eql(undefined);
+ expect(setIndexRule.index).toEqual(undefined);
+ expect(setIndexRule.data_view_id).toEqual(undefined);
});
it('should return error if all index patterns removed from a rule with data views and overwrite_data_views is true', async () => {
@@ -2394,8 +2466,8 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(500);
- expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
- expect(body.attributes.errors[0]).to.eql({
+ expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 });
+ expect(body.attributes.errors[0]).toEqual({
message: "Mutated params invalid: Index patterns can't be empty",
status_code: 500,
rules: [
@@ -2430,13 +2502,13 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 1, succeeded: 0, total: 1 });
- expect(body.attributes.errors).to.be(undefined);
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, total: 1 });
+ expect(body.attributes.errors).toBe(undefined);
// Check that the skipped rule is returned with the response
- expect(body.attributes.results.skipped[0].id).to.eql(rule.id);
- expect(body.attributes.results.skipped[0].name).to.eql(rule.name);
- expect(body.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED');
+ expect(body.attributes.results.skipped[0].id).toEqual(rule.id);
+ expect(body.attributes.results.skipped[0].name).toEqual(rule.name);
+ expect(body.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED');
});
});
@@ -2466,17 +2538,17 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].tags).to.eql(['tag1', 'tag2', 'tag3']);
- expect(body.attributes.results.updated[0].index).to.eql(['index1-*', 'initial-index-*']);
+ expect(body.attributes.results.updated[0].tags).toEqual(['tag1', 'tag2', 'tag3']);
+ expect(body.attributes.results.updated[0].index).toEqual(['index1-*', 'initial-index-*']);
// Check that the rule has been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.index).to.eql(['index1-*', 'initial-index-*']);
- expect(updatedRule.tags).to.eql(['tag1', 'tag2', 'tag3']);
+ expect(updatedRule.index).toEqual(['index1-*', 'initial-index-*']);
+ expect(updatedRule.tags).toEqual(['tag1', 'tag2', 'tag3']);
});
it('should return one updated rule when applying one valid operation and one operation to be skipped on a rule', async () => {
@@ -2506,17 +2578,17 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].tags).to.eql(['tag1', 'tag2']);
- expect(body.attributes.results.updated[0].index).to.eql(['index1-*', 'initial-index-*']);
+ expect(body.attributes.results.updated[0].tags).toEqual(['tag1', 'tag2']);
+ expect(body.attributes.results.updated[0].index).toEqual(['index1-*', 'initial-index-*']);
// Check that the rule has been persisted
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
- expect(updatedRule.index).to.eql(['index1-*', 'initial-index-*']);
- expect(updatedRule.tags).to.eql(['tag1', 'tag2']);
+ expect(updatedRule.index).toEqual(['index1-*', 'initial-index-*']);
+ expect(updatedRule.tags).toEqual(['tag1', 'tag2']);
});
it('should return one skipped rule when two (all) operations result in a no-op', async () => {
@@ -2546,18 +2618,18 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 1, succeeded: 0, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, total: 1 });
// Check that the skipped rule is returned with the response
- expect(body.attributes.results.skipped[0].name).to.eql(rule.name);
- expect(body.attributes.results.skipped[0].id).to.eql(rule.id);
- expect(body.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED');
+ expect(body.attributes.results.skipped[0].name).toEqual(rule.name);
+ expect(body.attributes.results.skipped[0].id).toEqual(rule.id);
+ expect(body.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED');
// Check that no change to the rule have been persisted
const { body: skippedRule } = await fetchRule(ruleId).expect(200);
- expect(skippedRule.index).to.eql(['index1-*']);
- expect(skippedRule.tags).to.eql(['tag1', 'tag2']);
+ expect(skippedRule.index).toEqual(['index1-*']);
+ expect(skippedRule.tags).toEqual(['tag1', 'tag2']);
});
});
@@ -2585,7 +2657,7 @@ export default ({ getService }: FtrProviderContext): void => {
)
);
- expect(responses.filter((r) => r.body.statusCode === 429).length).to.eql(5);
+ expect(responses.filter((r) => r.body.statusCode === 429).length).toEqual(5);
});
it('should bulk update rule by id', async () => {
@@ -2613,17 +2685,17 @@ export default ({ getService }: FtrProviderContext): void => {
})
.expect(200);
- expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
+ expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
- expect(body.attributes.results.updated[0].timeline_id).to.eql(timelineId);
- expect(body.attributes.results.updated[0].timeline_title).to.eql(timelineTitle);
+ expect(body.attributes.results.updated[0].timeline_id).toEqual(timelineId);
+ expect(body.attributes.results.updated[0].timeline_title).toEqual(timelineTitle);
// Check that the updates have been persisted
const { body: rule } = await fetchRule(ruleId).expect(200);
- expect(rule.timeline_id).to.eql(timelineId);
- expect(rule.timeline_title).to.eql(timelineTitle);
+ expect(rule.timeline_id).toEqual(timelineId);
+ expect(rule.timeline_title).toEqual(timelineTitle);
});
});
};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts
index 28a362213ae3b4..6ba0cc273c8c59 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts
@@ -5,11 +5,12 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine';
import {
getSimpleRule,
+ getCustomQueryRuleParams,
getSimpleRuleOutputWithoutRuleId,
getSimpleRuleWithoutRuleId,
removeServerGeneratedProperties,
@@ -65,7 +66,31 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare = removeServerGeneratedProperties(body);
const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
+ });
+
+ it('should create a rule with defaultable fields', async () => {
+ const expectedRule = getCustomQueryRuleParams({
+ rule_id: 'rule-1',
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ const { body: createdRuleResponse } = await securitySolutionApi
+ .createRule({ body: expectedRule })
+ .expect(200);
+
+ expect(createdRuleResponse).toMatchObject(expectedRule);
+
+ const { body: createdRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(createdRule).toMatchObject(expectedRule);
});
it('should create a single rule without an input index', async () => {
@@ -120,7 +145,7 @@ export default ({ getService }: FtrProviderContext) => {
ELASTICSEARCH_USERNAME
);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should create a single rule without a rule_id', async () => {
@@ -134,7 +159,7 @@ export default ({ getService }: FtrProviderContext) => {
ELASTICSEARCH_USERNAME
);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should cause a 409 conflict if we attempt to create the same rule_id twice', async () => {
@@ -144,7 +169,7 @@ export default ({ getService }: FtrProviderContext) => {
.createRule({ body: getSimpleRule() })
.expect(409);
- expect(body).to.eql({
+ expect(body).toEqual({
message: 'rule_id: "rule-1" already exists',
status_code: 409,
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts
index 3a1cdbbf373ed7..17b4ea3e3604e8 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts
@@ -5,13 +5,14 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import {
getSimpleRule,
getSimpleRuleOutput,
+ getCustomQueryRuleParams,
getSimpleRuleOutputWithoutRuleId,
getSimpleRuleWithoutRuleId,
removeServerGeneratedProperties,
@@ -64,7 +65,31 @@ export default ({ getService }: FtrProviderContext): void => {
const bodyToCompare = removeServerGeneratedProperties(body[0]);
const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
+ });
+
+ it('should create a rule with defaultable fields', async () => {
+ const expectedRule = getCustomQueryRuleParams({
+ rule_id: 'rule-1',
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ const { body: createdRulesBulkResponse } = await securitySolutionApi
+ .bulkCreateRules({ body: [expectedRule] })
+ .expect(200);
+
+ expect(createdRulesBulkResponse[0]).toMatchObject(expectedRule);
+
+ const { body: createdRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(createdRule).toMatchObject(expectedRule);
});
it('should create a single rule without a rule_id', async () => {
@@ -78,7 +103,7 @@ export default ({ getService }: FtrProviderContext): void => {
ELASTICSEARCH_USERNAME
);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id twice', async () => {
@@ -86,7 +111,7 @@ export default ({ getService }: FtrProviderContext): void => {
.bulkCreateRules({ body: [getSimpleRule(), getSimpleRule()] })
.expect(200);
- expect(body).to.eql([
+ expect(body).toEqual([
{
error: {
message: 'rule_id: "rule-1" already exists',
@@ -104,7 +129,7 @@ export default ({ getService }: FtrProviderContext): void => {
.bulkCreateRules({ body: [getSimpleRule()] })
.expect(200);
- expect(body).to.eql([
+ expect(body).toEqual([
{
error: {
message: 'rule_id: "rule-1" already exists',
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts
index f355e9ed61fc40..d91c1ab18b44ac 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts
@@ -8,6 +8,7 @@
import expect from 'expect';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
+import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { binaryToString, getCustomQueryRuleParams } from '../../../utils';
import {
@@ -18,6 +19,7 @@ import {
} from '../../../../../../common/utils/security_solution';
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
+ const securitySolutionApi = getService('securitySolutionApi');
const log = getService('log');
const es = getService('es');
@@ -63,6 +65,27 @@ export default ({ getService }: FtrProviderContext): void => {
expect(exportedRule).toMatchObject(ruleToExport);
});
+ it('should export defaultable fields when values are set', async () => {
+ const defaultableFields: BaseDefaultableFields = {
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ };
+ const ruleToExport = getCustomQueryRuleParams(defaultableFields);
+
+ await securitySolutionApi.createRule({ body: ruleToExport });
+
+ const { body } = await securitySolutionApi
+ .exportRules({ query: {}, body: null })
+ .expect(200)
+ .parse(binaryToString);
+
+ const exportedRule = JSON.parse(body.toString().split(/\n/)[0]);
+
+ expect(exportedRule).toMatchObject(defaultableFields);
+ });
+
it('should have export summary reflecting a number of rules', async () => {
await createRule(supertest, log, getCustomQueryRuleParams());
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts
index d1b2fb041f4bc3..f4fc373965df98 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts
@@ -8,6 +8,7 @@
import expect from 'expect';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
+import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { getCustomQueryRuleParams, combineToNdJson, fetchRule } from '../../../utils';
import {
@@ -19,6 +20,7 @@ import {
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
+ const securitySolutionApi = getService('securitySolutionApi');
const log = getService('log');
const es = getService('es');
@@ -135,6 +137,33 @@ export default ({ getService }: FtrProviderContext): void => {
expect(body.errors[0].error.message).toBe('from: Failed to parse date-math expression');
});
+ it('should be able to import rules with defaultable fields', async () => {
+ const defaultableFields: BaseDefaultableFields = {
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ };
+ const ruleToImport = getCustomQueryRuleParams({
+ ...defaultableFields,
+ rule_id: 'rule-1',
+ });
+ const ndjson = combineToNdJson(ruleToImport);
+
+ await securitySolutionApi
+ .importRules({ query: {} })
+ .attach('file', Buffer.from(ndjson), 'rules.ndjson')
+ .expect(200);
+
+ const { body: importedRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(importedRule).toMatchObject(ruleToImport);
+ });
+
it('should be able to import two rules', async () => {
const ndjson = combineToNdJson(
getCustomQueryRuleParams({
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts
index 7abca99e6e0522..27990708215d33 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts
@@ -5,12 +5,13 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import {
getSimpleRule,
getSimpleRuleOutput,
+ getCustomQueryRuleParams,
removeServerGeneratedProperties,
removeServerGeneratedPropertiesIncludingRuleId,
getSimpleRuleOutputWithoutRuleId,
@@ -56,7 +57,40 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
+ });
+
+ it('should patch defaultable fields', async () => {
+ const expectedRule = getCustomQueryRuleParams({
+ rule_id: 'rule-1',
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ await securitySolutionApi.createRule({
+ body: getCustomQueryRuleParams({ rule_id: 'rule-1' }),
+ });
+
+ const { body: patchedRuleResponse } = await securitySolutionApi
+ .patchRule({
+ body: {
+ rule_id: 'rule-1',
+ related_integrations: expectedRule.related_integrations,
+ },
+ })
+ .expect(200);
+
+ expect(patchedRuleResponse).toMatchObject(expectedRule);
+
+ const { body: patchedRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(patchedRule).toMatchObject(expectedRule);
});
it('@skipInServerless should return a "403 forbidden" using a rule_id of type "machine learning"', async () => {
@@ -67,7 +101,7 @@ export default ({ getService }: FtrProviderContext) => {
.patchRule({ body: { rule_id: 'rule-1', type: 'machine_learning' } })
.expect(403);
- expect(body).to.eql({
+ expect(body).toEqual({
message: 'Your license does not support machine learning. Please upgrade your license.',
status_code: 403,
});
@@ -90,7 +124,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should patch a single rule property of name using the auto-generated id', async () => {
@@ -107,7 +141,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should not change the revision of a rule when it patches only enabled', async () => {
@@ -123,7 +157,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should change the revision of a rule when it patches enabled and another property', async () => {
@@ -141,7 +175,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should not change other properties when it does patches', async () => {
@@ -167,7 +201,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should give a 404 if it is given a fake id', async () => {
@@ -177,7 +211,7 @@ export default ({ getService }: FtrProviderContext) => {
})
.expect(404);
- expect(body).to.eql({
+ expect(body).toEqual({
status_code: 404,
message: 'id: "5096dec6-b6b9-4d8d-8f93-6c2602079d9d" not found',
});
@@ -188,7 +222,7 @@ export default ({ getService }: FtrProviderContext) => {
.patchRule({ body: { rule_id: 'fake_id', name: 'some other name' } })
.expect(404);
- expect(body).to.eql({
+ expect(body).toEqual({
status_code: 404,
message: 'rule_id: "fake_id" not found',
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts
index bb86ae5d17354f..ef3c944bf9931f 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts
@@ -5,12 +5,13 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import {
getSimpleRule,
getSimpleRuleOutput,
+ getCustomQueryRuleParams,
removeServerGeneratedProperties,
getSimpleRuleOutputWithoutRuleId,
removeServerGeneratedPropertiesIncludingRuleId,
@@ -55,7 +56,42 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
+ });
+
+ it('should patch defaultable fields', async () => {
+ const expectedRule = getCustomQueryRuleParams({
+ rule_id: 'rule-1',
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ await securitySolutionApi.createRule({
+ body: getCustomQueryRuleParams({ rule_id: 'rule-1' }),
+ });
+
+ const { body: patchedRulesBulkResponse } = await securitySolutionApi
+ .bulkPatchRules({
+ body: [
+ {
+ rule_id: 'rule-1',
+ related_integrations: expectedRule.related_integrations,
+ },
+ ],
+ })
+ .expect(200);
+
+ expect(patchedRulesBulkResponse[0]).toMatchObject(expectedRule);
+
+ const { body: patchedRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(patchedRule).toMatchObject(expectedRule);
});
it('should patch two rule properties of name using the two rules rule_id', async () => {
@@ -84,8 +120,8 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare1 = removeServerGeneratedProperties(body[0]);
const bodyToCompare2 = removeServerGeneratedProperties(body[1]);
- expect(bodyToCompare1).to.eql(expectedRule1);
- expect(bodyToCompare2).to.eql(expectedRule2);
+ expect(bodyToCompare1).toEqual(expectedRule1);
+ expect(bodyToCompare2).toEqual(expectedRule2);
});
it('should patch a single rule property of name using an id', async () => {
@@ -101,7 +137,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.revision = 1;
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should patch two rule properties of name using the two rules id', async () => {
@@ -130,8 +166,8 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]);
const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]);
- expect(bodyToCompare1).to.eql(expectedRule);
- expect(bodyToCompare2).to.eql(expectedRule2);
+ expect(bodyToCompare1).toEqual(expectedRule);
+ expect(bodyToCompare2).toEqual(expectedRule2);
});
it('should patch a single rule property of name using the auto-generated id', async () => {
@@ -148,7 +184,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should not change the revision of a rule when it patches only enabled', async () => {
@@ -164,7 +200,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should change the revision of a rule when it patches enabled and another property', async () => {
@@ -182,7 +218,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should not change other properties when it does patches', async () => {
@@ -208,7 +244,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should return a 200 but give a 404 in the message if it is given a fake id', async () => {
@@ -218,7 +254,7 @@ export default ({ getService }: FtrProviderContext) => {
})
.expect(200);
- expect(body).to.eql([
+ expect(body).toEqual([
{
id: '5096dec6-b6b9-4d8d-8f93-6c2602079d9d',
error: {
@@ -234,7 +270,7 @@ export default ({ getService }: FtrProviderContext) => {
.bulkPatchRules({ body: [{ rule_id: 'fake_id', name: 'some other name' }] })
.expect(200);
- expect(body).to.eql([
+ expect(body).toEqual([
{
rule_id: 'fake_id',
error: { status_code: 404, message: 'rule_id: "fake_id" not found' },
@@ -261,7 +297,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect([bodyToCompare, body[1]]).to.eql([
+ expect([bodyToCompare, body[1]]).toEqual([
expectedRule,
{
error: {
@@ -292,7 +328,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect([bodyToCompare, body[1]]).to.eql([
+ expect([bodyToCompare, body[1]]).toEqual([
expectedRule,
{
error: {
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts
index 301b4413805a96..08dfdac9a7e82e 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts
@@ -5,11 +5,12 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import {
getSimpleRuleOutput,
+ getCustomQueryRuleParams,
removeServerGeneratedProperties,
removeServerGeneratedPropertiesIncludingRuleId,
getSimpleRuleOutputWithoutRuleId,
@@ -61,7 +62,37 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
+ });
+
+ it('should update a rule with defaultable fields', async () => {
+ const expectedRule = getCustomQueryRuleParams({
+ rule_id: 'rule-1',
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ await securitySolutionApi.createRule({
+ body: getCustomQueryRuleParams({ rule_id: 'rule-1' }),
+ });
+
+ const { body: updatedRuleResponse } = await securitySolutionApi
+ .updateRule({
+ body: expectedRule,
+ })
+ .expect(200);
+
+ expect(updatedRuleResponse).toMatchObject(expectedRule);
+
+ const { body: updatedRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(updatedRule).toMatchObject(expectedRule);
});
it('@skipInServerless should return a 403 forbidden if it is a machine learning job', async () => {
@@ -75,7 +106,7 @@ export default ({ getService }: FtrProviderContext) => {
const { body } = await securitySolutionApi.updateRule({ body: updatedRule }).expect(403);
- expect(body).to.eql({
+ expect(body).toEqual({
message: 'Your license does not support machine learning. Please upgrade your license.',
status_code: 403,
});
@@ -100,7 +131,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should update a single rule property of name using the auto-generated id', async () => {
@@ -120,7 +151,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should change the revision of a rule when it updates enabled and another property', async () => {
@@ -140,7 +171,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => {
@@ -165,7 +196,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should give a 404 if it is given a fake id', async () => {
@@ -175,7 +206,7 @@ export default ({ getService }: FtrProviderContext) => {
const { body } = await securitySolutionApi.updateRule({ body: simpleRule }).expect(404);
- expect(body).to.eql({
+ expect(body).toEqual({
status_code: 404,
message: 'id: "5096dec6-b6b9-4d8d-8f93-6c2602079d9d" not found',
});
@@ -188,7 +219,7 @@ export default ({ getService }: FtrProviderContext) => {
const { body } = await securitySolutionApi.updateRule({ body: simpleRule }).expect(404);
- expect(body).to.eql({
+ expect(body).toEqual({
status_code: 404,
message: 'rule_id: "fake_id" not found',
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts
index 4696a5d82444c5..d28d9efd413507 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts
@@ -5,11 +5,12 @@
* 2.0.
*/
-import expect from '@kbn/expect';
+import expect from 'expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import {
getSimpleRuleOutput,
+ getCustomQueryRuleParams,
removeServerGeneratedProperties,
getSimpleRuleOutputWithoutRuleId,
removeServerGeneratedPropertiesIncludingRuleId,
@@ -60,7 +61,37 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
+ });
+
+ it('should update a rule with defaultable fields', async () => {
+ const expectedRule = getCustomQueryRuleParams({
+ rule_id: 'rule-1',
+ related_integrations: [
+ { package: 'package-a', version: '^1.2.3' },
+ { package: 'package-b', integration: 'integration-b', version: '~1.1.1' },
+ ],
+ });
+
+ await securitySolutionApi.createRule({
+ body: getCustomQueryRuleParams({ rule_id: 'rule-1' }),
+ });
+
+ const { body: updatedRulesBulkResponse } = await securitySolutionApi
+ .bulkUpdateRules({
+ body: [expectedRule],
+ })
+ .expect(200);
+
+ expect(updatedRulesBulkResponse[0]).toMatchObject(expectedRule);
+
+ const { body: updatedRule } = await securitySolutionApi
+ .readRule({
+ query: { rule_id: 'rule-1' },
+ })
+ .expect(200);
+
+ expect(updatedRule).toMatchObject(expectedRule);
});
it('should update two rule properties of name using the two rules rule_id', async () => {
@@ -92,8 +123,8 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare1 = removeServerGeneratedProperties(body[0]);
const bodyToCompare2 = removeServerGeneratedProperties(body[1]);
- expect(bodyToCompare1).to.eql(expectedRule);
- expect(bodyToCompare2).to.eql(expectedRule2);
+ expect(bodyToCompare1).toEqual(expectedRule);
+ expect(bodyToCompare2).toEqual(expectedRule2);
});
it('should update a single rule property of name using an id', async () => {
@@ -115,7 +146,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should update two rule properties of name using the two rules id', async () => {
@@ -149,8 +180,8 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]);
const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]);
- expect(bodyToCompare1).to.eql(expectedRule);
- expect(bodyToCompare2).to.eql(expectedRule2);
+ expect(bodyToCompare1).toEqual(expectedRule);
+ expect(bodyToCompare2).toEqual(expectedRule2);
});
it('should update a single rule property of name using the auto-generated id', async () => {
@@ -172,7 +203,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should change the revision of a rule when it updates enabled and another property', async () => {
@@ -194,7 +225,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => {
@@ -221,7 +252,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect(bodyToCompare).to.eql(expectedRule);
+ expect(bodyToCompare).toEqual(expectedRule);
});
it('should return a 200 but give a 404 in the message if it is given a fake id', async () => {
@@ -233,7 +264,7 @@ export default ({ getService }: FtrProviderContext) => {
.bulkUpdateRules({ body: [ruleUpdate] })
.expect(200);
- expect(body).to.eql([
+ expect(body).toEqual([
{
id: '1fd52120-d3a9-4e7a-b23c-96c0e1a74ae5',
error: {
@@ -253,7 +284,7 @@ export default ({ getService }: FtrProviderContext) => {
.bulkUpdateRules({ body: [ruleUpdate] })
.expect(200);
- expect(body).to.eql([
+ expect(body).toEqual([
{
rule_id: 'fake_id',
error: { status_code: 404, message: 'rule_id: "fake_id" not found' },
@@ -283,7 +314,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect([bodyToCompare, body[1]]).to.eql([
+ expect([bodyToCompare, body[1]]).toEqual([
expectedRule,
{
error: {
@@ -319,7 +350,7 @@ export default ({ getService }: FtrProviderContext) => {
const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME);
const bodyToCompare = removeServerGeneratedProperties(body[0]);
- expect([bodyToCompare, body[1]]).to.eql([
+ expect([bodyToCompare, body[1]]).toEqual([
expectedRule,
{
error: {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts
index c9d83ae4d67e1f..a5903af58f1ee9 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts
@@ -27,6 +27,7 @@ import {
fillFrom,
fillNote,
fillReferenceUrls,
+ fillRelatedIntegrations,
fillRiskScore,
fillRuleName,
fillRuleTags,
@@ -59,6 +60,7 @@ describe('Common rule creation flows', { tags: ['@ess', '@serverless'] }, () =>
it('Creates and enables a rule', function () {
cy.log('Filling define section');
importSavedQuery(this.timelineId);
+ fillRelatedIntegrations();
cy.get(DEFINE_CONTINUE_BUTTON).click();
cy.log('Filling about section');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts
index 413504800c2a7d..06e73f78ac2ad4 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts
@@ -5,8 +5,10 @@
* 2.0.
*/
-import { omit } from 'lodash';
-import { PerformRuleInstallationResponseBody } from '@kbn/security-solution-plugin/common/api/detection_engine';
+import {
+ PerformRuleInstallationResponseBody,
+ RelatedIntegration,
+} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { generateEvent } from '../../../../objects/event';
import { createDocument, deleteDataStream } from '../../../../tasks/api_calls/elasticsearch';
import { createRuleAssetSavedObject } from '../../../../helpers/rules';
@@ -50,37 +52,59 @@ import {
describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
const DATA_STREAM_NAME = 'logs-related-integrations-test';
const PREBUILT_RULE_NAME = 'Prebuilt rule with related integrations';
- const RULE_RELATED_INTEGRATIONS: IntegrationDefinition[] = [
+ const RELATED_INTEGRATIONS: RelatedIntegration[] = [
+ {
+ package: 'auditd',
+ version: '1.16.0',
+ },
{
package: 'aws',
version: '1.17.0',
integration: 'cloudfront',
- installed: true,
- enabled: true,
},
{
package: 'aws',
version: '1.17.0',
integration: 'cloudtrail',
- installed: true,
- enabled: false,
},
{
package: 'aws',
version: '1.17.0',
integration: 'unknown',
- installed: false,
- enabled: false,
},
- { package: 'system', version: '1.17.0', installed: true, enabled: true },
+ { package: 'system', version: '1.17.0' },
];
const PREBUILT_RULE = createRuleAssetSavedObject({
name: PREBUILT_RULE_NAME,
index: [DATA_STREAM_NAME],
query: '*:*',
rule_id: 'rule_1',
- related_integrations: RULE_RELATED_INTEGRATIONS.map((x) => omit(x, ['installed', 'enabled'])),
+ related_integrations: RELATED_INTEGRATIONS,
});
+ const EXPECTED_RELATED_INTEGRATIONS: ExpectedRelatedIntegration[] = [
+ {
+ title: 'Auditd Logs',
+ status: 'Not installed',
+ },
+ {
+ title: 'AWS Amazon cloudfront',
+ status: 'Enabled',
+ },
+ {
+ title: 'AWS Aws cloudtrail',
+ status: 'Disabled',
+ },
+ {
+ title: 'Aws Unknown',
+ },
+ {
+ title: 'System',
+ status: 'Enabled',
+ },
+ ];
+ const EXPECTED_KNOWN_RELATED_INTEGRATIONS = EXPECTED_RELATED_INTEGRATIONS.filter((x) =>
+ Boolean(x.status)
+ );
beforeEach(() => {
login();
@@ -99,7 +123,7 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
it('should display a badge with the installed integrations', () => {
cy.get(INTEGRATIONS_POPOVER).should(
'have.text',
- `0/${RULE_RELATED_INTEGRATIONS.length} integrations`
+ `0/${EXPECTED_RELATED_INTEGRATIONS.length} integrations`
);
});
@@ -108,15 +132,19 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
cy.get(INTEGRATIONS_POPOVER_TITLE).should(
'have.text',
- `[${RULE_RELATED_INTEGRATIONS.length}] Related integrations available`
+ `[${EXPECTED_RELATED_INTEGRATIONS.length}] Related integrations available`
+ );
+ cy.get(INTEGRATION_LINK).should('have.length', EXPECTED_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_STATUS).should(
+ 'have.length',
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.length
);
- cy.get(INTEGRATION_LINK).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- cy.get(INTEGRATION_STATUS).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- RULE_RELATED_INTEGRATIONS.forEach((integration, index) => {
- cy.get(INTEGRATION_LINK).eq(index).contains(getIntegrationName(integration), {
- matchCase: false,
- });
+ EXPECTED_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_LINK).eq(index).contains(expected.title);
+ });
+
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.forEach((_, index) => {
cy.get(INTEGRATION_STATUS).eq(index).should('have.text', 'Not installed');
});
});
@@ -128,13 +156,17 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
});
it('should display the integrations in the definition section', () => {
- cy.get(INTEGRATION_LINK).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- cy.get(INTEGRATION_STATUS).should('have.length', RULE_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_LINK).should('have.length', EXPECTED_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_STATUS).should(
+ 'have.length',
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.length
+ );
- RULE_RELATED_INTEGRATIONS.forEach((integration, index) => {
- cy.get(INTEGRATION_LINK).eq(index).contains(getIntegrationName(integration), {
- matchCase: false,
- });
+ EXPECTED_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_LINK).eq(index).contains(expected.title);
+ });
+
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.forEach((_, index) => {
cy.get(INTEGRATION_STATUS).eq(index).should('have.text', 'Not installed');
});
});
@@ -165,12 +197,9 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
});
it('should display a badge with the installed integrations', () => {
- const enabledIntegrations = RULE_RELATED_INTEGRATIONS.filter((x) => x.enabled).length;
- const totalIntegrations = RULE_RELATED_INTEGRATIONS.length;
-
cy.get(INTEGRATIONS_POPOVER).should(
'have.text',
- `${enabledIntegrations}/${totalIntegrations} integrations`
+ `2/${EXPECTED_RELATED_INTEGRATIONS.length} integrations`
);
});
@@ -179,18 +208,20 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
cy.get(INTEGRATIONS_POPOVER_TITLE).should(
'have.text',
- `[${RULE_RELATED_INTEGRATIONS.length}] Related integrations available`
+ `[${EXPECTED_RELATED_INTEGRATIONS.length}] Related integrations available`
+ );
+ cy.get(INTEGRATION_LINK).should('have.length', EXPECTED_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_STATUS).should(
+ 'have.length',
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.length
);
- cy.get(INTEGRATION_LINK).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- cy.get(INTEGRATION_STATUS).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- RULE_RELATED_INTEGRATIONS.forEach((integration, index) => {
- cy.get(INTEGRATION_LINK).eq(index).contains(getIntegrationName(integration), {
- matchCase: false,
- });
- cy.get(INTEGRATION_STATUS)
- .eq(index)
- .should('have.text', getIntegrationStatus(integration));
+ EXPECTED_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_LINK).eq(index).contains(expected.title);
+ });
+
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_STATUS).eq(index).should('have.text', expected.status);
});
});
});
@@ -202,16 +233,18 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
});
it('should display the integrations in the definition section', () => {
- cy.get(INTEGRATION_LINK).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- cy.get(INTEGRATION_STATUS).should('have.length', RULE_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_LINK).should('have.length', EXPECTED_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_STATUS).should(
+ 'have.length',
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.length
+ );
- RULE_RELATED_INTEGRATIONS.forEach((integration, index) => {
- cy.get(INTEGRATION_LINK).eq(index).contains(getIntegrationName(integration), {
- matchCase: false,
- });
- cy.get(INTEGRATION_STATUS)
- .eq(index)
- .should('have.text', getIntegrationStatus(integration));
+ EXPECTED_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_LINK).eq(index).contains(expected.title);
+ });
+
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_STATUS).eq(index).should('have.text', expected.status);
});
});
@@ -230,9 +263,7 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
size: 1,
}).then((alertsResponse) => {
expect(alertsResponse.body.hits.hits[0].fields).to.deep.equal({
- [RELATED_INTEGRATION_FIELD]: RULE_RELATED_INTEGRATIONS.map((x) =>
- omit(x, ['installed', 'enabled'])
- ),
+ [RELATED_INTEGRATION_FIELD]: RELATED_INTEGRATIONS,
});
});
});
@@ -263,13 +294,17 @@ describe('Related integrations', { tags: ['@ess', '@serverless', '@skipInServerl
});
it('should display the integrations in the definition section', () => {
- cy.get(INTEGRATION_LINK).should('have.length', RULE_RELATED_INTEGRATIONS.length);
- cy.get(INTEGRATION_STATUS).should('have.length', RULE_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_LINK).should('have.length', EXPECTED_RELATED_INTEGRATIONS.length);
+ cy.get(INTEGRATION_STATUS).should(
+ 'have.length',
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.length
+ );
- RULE_RELATED_INTEGRATIONS.forEach((integration, index) => {
- cy.get(INTEGRATION_LINK).eq(index).contains(getIntegrationName(integration), {
- matchCase: false,
- });
+ EXPECTED_RELATED_INTEGRATIONS.forEach((expected, index) => {
+ cy.get(INTEGRATION_LINK).eq(index).contains(expected.title);
+ });
+
+ EXPECTED_KNOWN_RELATED_INTEGRATIONS.forEach((_, index) => {
cy.get(INTEGRATION_STATUS).eq(index).should('have.text', 'Not installed');
});
});
@@ -290,22 +325,9 @@ function visitFirstInstalledPrebuiltRuleDetailsPage(): void {
).then((response) => visitRuleDetailsPage(response.body.results.created[0].id));
}
-interface IntegrationDefinition {
- package: string;
- version: string;
- installed: boolean;
- enabled: boolean;
- integration?: string;
-}
-
-function getIntegrationName(integration: IntegrationDefinition): string {
- return `${integration.package} ${integration.integration ?? ''}`.trim();
-}
-
-function getIntegrationStatus(integration: IntegrationDefinition): string {
- return `${integration.installed ? 'Installed' : 'Not installed'}${
- integration.enabled ? ': enabled' : ''
- }`.trim();
+interface ExpectedRelatedIntegration {
+ title: string;
+ status?: string;
}
/**
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts
index 0b98ae36f1ec9c..9ccedac0c2504a 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts
@@ -32,7 +32,7 @@ import {
ENTITY_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR,
} from '../../screens/asset_criticality/flyouts';
import { deleteCriticality } from '../../tasks/api_calls/entity_analytics';
-import { mockFleetInstalledIntegrations } from '../../tasks/fleet_integrations';
+import { mockFleetIntegrations } from '../../tasks/fleet_integrations';
import {
expandManagedDataEntraPanel,
expandManagedDataOktaPanel,
@@ -146,18 +146,22 @@ describe(
// https://github.com/elastic/kibana/issues/179248
describe('Managed data section', { tags: ['@skipInServerlessMKI'] }, () => {
beforeEach(() => {
- mockFleetInstalledIntegrations([
+ mockFleetIntegrations([
{
package_name: ENTRA_ID_PACKAGE_NAME,
- is_enabled: true,
package_title: 'azure entra',
- package_version: 'test_package_version',
+ latest_package_version: 'test_package_version',
+ installed_package_version: 'test_package_version',
+ is_installed: true,
+ is_enabled: true,
},
{
package_name: OKTA_PACKAGE_NAME,
- is_enabled: true,
package_title: 'okta',
- package_version: 'test_package_version',
+ latest_package_version: 'test_package_version',
+ installed_package_version: 'test_package_version',
+ is_installed: true,
+ is_enabled: true,
},
]);
});
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/common.ts b/x-pack/test/security_solution_cypress/cypress/screens/common.ts
index 3d6aae97850188..b121badc9e20d7 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/common.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/common.ts
@@ -8,3 +8,5 @@
export const TOOLTIP = '[role="tooltip"]';
export const BASIC_TABLE_LOADING = '.euiBasicTable.euiBasicTable-loading';
+
+export const COMBO_BOX_OPTION = '.euiComboBoxOptionsList button[role="option"]';
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts
index ac474a56bd5b35..bf88869973ceed 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts
@@ -125,6 +125,9 @@ export const EQL_OPTIONS_TIMESTAMP_INPUT =
export const IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK =
'[data-test-subj="importQueryFromSavedTimeline"]';
+export const RELATED_INTEGRATION_COMBO_BOX_INPUT =
+ '[data-test-subj="relatedIntegrationComboBox"] [data-test-subj="comboBoxSearchInput"]';
+
export const INDICATOR_MATCH_TYPE = '[data-test-subj="threatMatchRuleType"]';
export const INPUT = '[data-test-subj="input"]';
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
index 091d592dd04aea..f40cecee5a9815 100644
--- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
@@ -125,6 +125,7 @@ import {
ALERTS_INDEX_BUTTON,
INVESTIGATIONS_INPUT,
QUERY_BAR_ADD_FILTER,
+ RELATED_INTEGRATION_COMBO_BOX_INPUT,
} from '../screens/create_new_rule';
import {
INDEX_SELECTOR,
@@ -147,7 +148,7 @@ import { ruleFields } from '../data/detection_engine';
import { waitForAlerts } from './alerts';
import { refreshPage } from './security_header';
import { EMPTY_ALERT_TABLE } from '../screens/alerts';
-import { TOOLTIP } from '../screens/common';
+import { COMBO_BOX_OPTION, TOOLTIP } from '../screens/common';
export const createAndEnableRule = () => {
cy.get(CREATE_AND_ENABLE_BTN).click();
@@ -272,6 +273,17 @@ export const importSavedQuery = (timelineId: string) => {
removeAlertsIndex();
};
+export const fillRelatedIntegrations = (): void => {
+ addFirstIntegration();
+ addFirstIntegration();
+};
+
+const addFirstIntegration = (): void => {
+ cy.get('button').contains('Add integration').click();
+ cy.get(RELATED_INTEGRATION_COMBO_BOX_INPUT).last().should('be.enabled').click();
+ cy.get(COMBO_BOX_OPTION).first().click();
+};
+
export const fillRuleName = (ruleName: string = ruleFields.ruleName) => {
cy.get(RULE_NAME_INPUT).clear({ force: true });
cy.get(RULE_NAME_INPUT).type(ruleName, { force: true });
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts
index b60c6a8c5622fa..7105ebc4df70f2 100644
--- a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts
@@ -6,19 +6,19 @@
*/
import {
- GET_INSTALLED_INTEGRATIONS_URL,
- InstalledIntegration,
+ GET_ALL_INTEGRATIONS_URL,
+ Integration,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { login } from './login';
import { visitGetStartedPage } from './navigation';
-export const mockFleetInstalledIntegrations = (integrations: InstalledIntegration[] = []) => {
- cy.intercept('GET', `${GET_INSTALLED_INTEGRATIONS_URL}*`, {
+export const mockFleetIntegrations = (integrations: Integration[] = []) => {
+ cy.intercept('GET', `${GET_ALL_INTEGRATIONS_URL}*`, {
statusCode: 200,
body: {
- installed_integrations: integrations,
+ integrations,
},
- }).as('installedIntegrations');
+ }).as('integrations');
};
export const waitForFleetSetup = () => {