) => {
return services;
};
-const theme$ = of({ darkMode: false });
-
export function WithServices(Comp: ComponentType
, overrides: Partial = {}) {
return (props: P) => {
const services = getMockServices(overrides);
return (
- undefined} theme$={theme$}>
+ undefined}>
diff --git a/packages/core/http/core-http-router-server-internal/src/router.ts b/packages/core/http/core-http-router-server-internal/src/router.ts
index c265a7590d958..5177464796291 100644
--- a/packages/core/http/core-http-router-server-internal/src/router.ts
+++ b/packages/core/http/core-http-router-server-internal/src/router.ts
@@ -201,7 +201,6 @@ export class Router = ({
return (
-
+
diff --git a/packages/kbn-search-api-panels/components/install_client.tsx b/packages/kbn-search-api-panels/components/install_client.tsx
index 6d56f69e09530..7fa0ecee6a049 100644
--- a/packages/kbn-search-api-panels/components/install_client.tsx
+++ b/packages/kbn-search-api-panels/components/install_client.tsx
@@ -96,15 +96,19 @@ export const InstallClientPanel: React.FC = ({
defaultMessage:
'Elastic builds and maintains clients in several popular languages and our community has contributed many more. Install your favorite language client to get started.',
})}
- links={[
- {
- href: language.docLink,
- label: i18n.translate('searchApiPanels.welcomeBanner.installClient.clientDocLink', {
- defaultMessage: '{languageName} client documentation',
- values: { languageName: language.name },
- }),
- },
- ]}
+ links={
+ language.docLink
+ ? [
+ {
+ href: language.docLink,
+ label: i18n.translate('searchApiPanels.welcomeBanner.installClient.clientDocLink', {
+ defaultMessage: '{languageName} client documentation',
+ values: { languageName: language.name },
+ }),
+ },
+ ]
+ : []
+ }
title={i18n.translate('searchApiPanels.welcomeBanner.installClient.title', {
defaultMessage: 'Install a client',
})}
diff --git a/packages/kbn-search-api-panels/index.tsx b/packages/kbn-search-api-panels/index.tsx
index 5fa35ac35ef68..67bc9b221bdab 100644
--- a/packages/kbn-search-api-panels/index.tsx
+++ b/packages/kbn-search-api-panels/index.tsx
@@ -54,10 +54,14 @@ export const WelcomeBanner: React.FC = ({
- {i18n.translate('searchApiPanels.welcomeBanner.header.greeting.title', {
- defaultMessage: 'Hi {name}!',
- values: { name: user?.full_name || user.username },
- })}
+ {user
+ ? i18n.translate('searchApiPanels.welcomeBanner.header.greeting.customTitle', {
+ defaultMessage: 'Hi {name}!',
+ values: { name: user.full_name || user.username },
+ })
+ : i18n.translate('searchApiPanels.welcomeBanner.header.greeting.defaultTitle', {
+ defaultMessage: 'Hi!',
+ })}
diff --git a/packages/kbn-search-api-panels/languages/console.ts b/packages/kbn-search-api-panels/languages/console.ts
index be924d5fa3cbf..e156409239242 100644
--- a/packages/kbn-search-api-panels/languages/console.ts
+++ b/packages/kbn-search-api-panels/languages/console.ts
@@ -8,6 +8,8 @@
import { LanguageDefinition } from '../types';
+const INDEX_NAME_PLACEHOLDER = 'index_name';
+
export const consoleDefinition: Partial = {
buildSearchQuery: `POST /books/_search?pretty
{
@@ -30,4 +32,8 @@ export const consoleDefinition: Partial = {
{"name": "Brave New World", "author": "Aldous Huxley", "release_date": "1932-06-01", "page_count": 268}
{ "index" : { "_index" : "books" } }
{"name": "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311}`,
+ ingestDataIndex: ({ indexName }) => `POST _bulk?pretty
+{ "index" : { "_index" : "${indexName ?? INDEX_NAME_PLACEHOLDER}" } }
+{"name": "foo", "title": "bar"}
+`,
};
diff --git a/packages/kbn-search-api-panels/types.ts b/packages/kbn-search-api-panels/types.ts
index 5aba2d7b46bc0..774dd6c4bc9f0 100644
--- a/packages/kbn-search-api-panels/types.ts
+++ b/packages/kbn-search-api-panels/types.ts
@@ -30,22 +30,22 @@ export interface LanguageDefinitionSnippetArguments {
type CodeSnippet = string | ((args: LanguageDefinitionSnippetArguments) => string);
export interface LanguageDefinition {
+ name: string;
+ id: Languages;
+ iconType: string;
+ docLink?: string;
+ configureClient?: CodeSnippet;
+ ingestData?: CodeSnippet;
+ ingestDataIndex?: CodeSnippet;
+ installClient?: string;
+ buildSearchQuery?: CodeSnippet;
+ testConnection?: CodeSnippet;
advancedConfig?: string;
apiReference?: string;
basicConfig?: string;
- configureClient: CodeSnippet;
- docLink: string;
github?: {
link: string;
label: string;
};
- iconType: string;
- id: Languages;
- ingestData: CodeSnippet;
- ingestDataIndex: CodeSnippet;
- installClient: string;
languageStyling?: string;
- name: string;
- buildSearchQuery: CodeSnippet;
- testConnection: CodeSnippet;
}
diff --git a/packages/kbn-search-api-panels/utils.test.ts b/packages/kbn-search-api-panels/utils.test.ts
new file mode 100644
index 0000000000000..c842dd03cf275
--- /dev/null
+++ b/packages/kbn-search-api-panels/utils.test.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 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 { consoleDefinition } from './languages/console';
+import { getConsoleRequest } from './utils';
+
+describe('utils', () => {
+ describe('getConsoleRequest()', () => {
+ test('accepts string values', () => {
+ const consoleRequest = getConsoleRequest('ingestData');
+ expect(consoleRequest).toEqual(consoleDefinition.ingestData);
+ });
+
+ test('accepts function values', () => {
+ const consoleRequest = getConsoleRequest('ingestDataIndex', {
+ url: 'https://your_deployment_url',
+ apiKey: 'yourApiKey',
+ indexName: 'test-index',
+ });
+ expect(consoleRequest).toContain(`POST _bulk?pretty
+{ \"index\" : { \"_index\" : \"test-index\" } }
+{\"name\": \"foo\", \"title\": \"bar\"}`);
+ });
+
+ test('returns undefined if language definition is undefined', () => {
+ // @ts-ignore TS should not allow an invalid language definition
+ // We add @ts-ignore here to test the safeguard
+ const consoleRequest = getConsoleRequest('nonExistentRequest');
+ expect(consoleRequest).toEqual(undefined);
+ });
+ });
+});
diff --git a/packages/kbn-search-api-panels/utils.ts b/packages/kbn-search-api-panels/utils.ts
index ffd81257c5a30..6cc16439ea2f5 100644
--- a/packages/kbn-search-api-panels/utils.ts
+++ b/packages/kbn-search-api-panels/utils.ts
@@ -26,7 +26,23 @@ export const getLanguageDefinitionCodeSnippet = (
}
};
-export const getConsoleRequest = (code: keyof LanguageDefinition): string | undefined =>
- code in consoleDefinition && typeof consoleDefinition[code] === 'string'
- ? (consoleDefinition[code] as string)
- : undefined;
+export const getConsoleRequest = (
+ code: keyof LanguageDefinition,
+ args?: LanguageDefinitionSnippetArguments
+): string | undefined => {
+ if (code in consoleDefinition) {
+ const codeType = consoleDefinition[code];
+
+ switch (typeof codeType) {
+ case 'string':
+ return codeType as string;
+ case 'function':
+ if (args) {
+ return codeType(args) as string;
+ }
+ return undefined;
+ default:
+ return undefined;
+ }
+ }
+};
diff --git a/src/core/server/integration_tests/http/router.test.ts b/src/core/server/integration_tests/http/router.test.ts
index de5e10d1ee599..fdfbb76effbcf 100644
--- a/src/core/server/integration_tests/http/router.test.ts
+++ b/src/core/server/integration_tests/http/router.test.ts
@@ -568,6 +568,7 @@ describe('Handler', () => {
router.get({ path: '/', validate: false }, (context, req, res) => {
throw new Error('unexpected error');
});
+
await server.start();
const result = await supertest(innerServer.listener).get('/').expect(500);
@@ -575,21 +576,9 @@ describe('Handler', () => {
expect(result.body.message).toBe(
'An internal server error occurred. Check Kibana server logs for details.'
);
- expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(`
- Array [
- Array [
- "500 Server Error - /",
- Object {
- "error": [Error: unexpected error],
- "http": Object {
- "response": Object {
- "status_code": 500,
- },
- },
- },
- ],
- ]
- `);
+
+ const [message] = loggingSystemMock.collect(logger).error[0];
+ expect(message).toEqual('500 Server Error - /');
});
it('captures the error if handler throws', async () => {
@@ -627,7 +616,6 @@ describe('Handler', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: Unauthorized],
"http": Object {
"response": Object {
"status_code": 500,
@@ -657,7 +645,6 @@ describe('Handler', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: Unexpected result from Route Handler. Expected KibanaResponse, but given: string.],
"http": Object {
"response": Object {
"status_code": 500,
@@ -702,7 +689,6 @@ describe('Handler', () => {
Array [
"400 Bad Request - /",
Object {
- "error": [Error: [request query.page]: expected value of type [number] but got [string]],
"http": Object {
"response": Object {
"status_code": 400,
@@ -1187,7 +1173,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: expected 'location' header to be set],
"http": Object {
"response": Object {
"status_code": 500,
@@ -1601,7 +1586,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: Unexpected Http status code. Expected from 400 to 599, but given: 200],
"http": Object {
"response": Object {
"status_code": 500,
@@ -1678,7 +1662,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: expected 'location' header to be set],
"http": Object {
"response": Object {
"status_code": 500,
@@ -1826,7 +1809,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: expected error message to be provided],
"http": Object {
"response": Object {
"status_code": 500,
@@ -1860,7 +1842,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: expected error message to be provided],
"http": Object {
"response": Object {
"status_code": 500,
@@ -1893,7 +1874,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: options.statusCode is expected to be set. given options: undefined],
"http": Object {
"response": Object {
"status_code": 500,
@@ -1926,7 +1906,6 @@ describe('Response factory', () => {
Array [
"500 Server Error - /",
Object {
- "error": [Error: Unexpected Http status code. Expected from 100 to 599, but given: 20.],
"http": Object {
"response": Object {
"status_code": 500,
diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js
index fa03617f5824f..e8953152a5932 100644
--- a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js
+++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js
@@ -2090,6 +2090,12 @@ ace.define(
case 'p':
next('p');
switch (ch) {
+ case 'a':
+ next('a');
+ next('t');
+ next('c');
+ next('h');
+ return 'patch';
case 'u':
next('u');
next('t');
@@ -2106,6 +2112,12 @@ ace.define(
case 'P':
next('P');
switch (ch) {
+ case 'A':
+ next('A');
+ next('T');
+ next('C');
+ next('H');
+ return 'PATCH';
case 'U':
next('U');
next('T');
@@ -2120,7 +2132,7 @@ ace.define(
}
break;
default:
- error('Expected one of GET/POST/PUT/DELETE/HEAD');
+ error('Expected one of GET/POST/PUT/DELETE/HEAD/PATCH');
}
},
value, // Place holder for the value function.
@@ -2254,7 +2266,7 @@ ace.define(
annotate('error', e.message);
// snap
const substring = text.substr(at);
- const nextMatch = substring.search(/^POST|HEAD|GET|PUT|DELETE/m);
+ const nextMatch = substring.search(/^POST|HEAD|GET|PUT|DELETE|PATCH/m);
if (nextMatch < 1) return;
reset(at + nextMatch);
}
diff --git a/src/plugins/console/public/application/models/sense_editor/curl.ts b/src/plugins/console/public/application/models/sense_editor/curl.ts
index 74cbebf051d03..894ee2bf70168 100644
--- a/src/plugins/console/public/application/models/sense_editor/curl.ts
+++ b/src/plugins/console/public/application/models/sense_editor/curl.ts
@@ -38,13 +38,13 @@ export function parseCURL(text: string) {
const EscapedQuotes = /^((?:[^\\"']|\\.)+)/;
const LooksLikeCurl = /^\s*curl\s+/;
- const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/;
+ const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/;
const HasProtocol = /[\s"']https?:\/\//;
const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/;
const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/;
const CurlData = /^.+\s(--data|-d)\s*/;
- const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/;
+ const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/;
if (lines.length > 0 && ExecutionComment.test(lines[0])) {
lines.shift();
diff --git a/src/plugins/console/public/application/models/sense_editor/integration.test.js b/src/plugins/console/public/application/models/sense_editor/integration.test.js
index cd7e13d5c6a56..e47439a899edd 100644
--- a/src/plugins/console/public/application/models/sense_editor/integration.test.js
+++ b/src/plugins/console/public/application/models/sense_editor/integration.test.js
@@ -985,7 +985,7 @@ describe('Integration', () => {
{
name: 'Cursor rows after request end',
cursor: { lineNumber: 5, column: 1 },
- autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'],
+ autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'],
prefixToAdd: '',
suffixToAdd: ' ',
},
diff --git a/src/plugins/console/public/lib/autocomplete/autocomplete.ts b/src/plugins/console/public/lib/autocomplete/autocomplete.ts
index 167a0e0ab1bd3..74d06cd21ed70 100644
--- a/src/plugins/console/public/lib/autocomplete/autocomplete.ts
+++ b/src/plugins/console/public/lib/autocomplete/autocomplete.ts
@@ -967,7 +967,7 @@ export default function ({
}
function addMethodAutoCompleteSetToContext(context: AutoCompleteContext) {
- context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'].map((m, i) => ({
+ context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'].map((m, i) => ({
name: m,
score: -i,
meta: i18n.translate('console.autocomplete.addMethodMetaText', { defaultMessage: 'method' }),
diff --git a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js
index f2666052b988f..8d6a0a8f60b12 100644
--- a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js
+++ b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js
@@ -33,7 +33,7 @@ export class UrlPatternMatcher {
// We'll group endpoints by the methods which are attached to them,
//to avoid suggesting endpoints that are incompatible with the
//method that the user has entered.
- ['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => {
+ ['HEAD', 'GET', 'PUT', 'POST', 'DELETE', 'PATCH'].forEach((method) => {
this[method] = {
rootComponent: new SharedComponent('ROOT'),
parametrizedComponentFactories: parametrizedComponentFactories || {
diff --git a/src/plugins/console/public/lib/curl_parsing/curl.js b/src/plugins/console/public/lib/curl_parsing/curl.js
index 1ae6335f3249e..519cb3a3bbd0a 100644
--- a/src/plugins/console/public/lib/curl_parsing/curl.js
+++ b/src/plugins/console/public/lib/curl_parsing/curl.js
@@ -38,13 +38,13 @@ export function parseCURL(text) {
const EscapedQuotes = /^((?:[^\\"']|\\.)+)/;
const LooksLikeCurl = /^\s*curl\s+/;
- const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/;
+ const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/;
const HasProtocol = /[\s"']https?:\/\//;
const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/;
const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/;
const CurlData = /^.+\s(--data|-d)\s*/;
- const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/;
+ const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/;
if (lines.length > 0 && ExecutionComment.test(lines[0])) {
lines.shift();
diff --git a/src/plugins/console/server/routes/api/console/proxy/body.test.ts b/src/plugins/console/server/routes/api/console/proxy/body.test.ts
index 893f00f975e89..5500be776dcbc 100644
--- a/src/plugins/console/server/routes/api/console/proxy/body.test.ts
+++ b/src/plugins/console/server/routes/api/console/proxy/body.test.ts
@@ -90,5 +90,11 @@ describe('Console Proxy Route', () => {
});
});
});
+ describe('PATCH request', () => {
+ it('returns the exact body', async () => {
+ const { payload } = await request('PATCH', '/', 'foobar');
+ expect(await readStream(payload)).toBe('foobar');
+ });
+ });
});
});
diff --git a/src/plugins/console/server/routes/api/console/proxy/validation_config.ts b/src/plugins/console/server/routes/api/console/proxy/validation_config.ts
index 4492863a16bcb..9a3ee2efd66c1 100644
--- a/src/plugins/console/server/routes/api/console/proxy/validation_config.ts
+++ b/src/plugins/console/server/routes/api/console/proxy/validation_config.ts
@@ -13,11 +13,11 @@ export type Body = TypeOf;
const acceptedHttpVerb = schema.string({
validate: (method) => {
- return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE'].some(
+ return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'].some(
(verb) => verb.toLowerCase() === method.toLowerCase()
)
? undefined
- : `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE']. Received '${method}'.`;
+ : `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']. Received '${method}'.`;
},
});
diff --git a/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts b/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts
index af3108ddafce9..361a9c74c0090 100644
--- a/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts
+++ b/src/plugins/home/server/services/sample_data/sample_data_installer.test.ts
@@ -159,8 +159,14 @@ describe('SampleDataInstaller', () => {
expect(esClient.asCurrentUser.indices.create).toHaveBeenCalledWith({
index: 'kibana_sample_data_test_single_data_index',
body: {
- settings: { index: { number_of_shards: 1, auto_expand_replicas: '0-1' } },
- mappings: { properties: { someField: { type: 'keyword' } } },
+ mappings: {
+ properties: {
+ someField: { type: 'keyword' },
+ },
+ },
+ settings: {
+ index: {},
+ },
},
});
});
diff --git a/src/plugins/home/server/services/sample_data/sample_data_installer.ts b/src/plugins/home/server/services/sample_data/sample_data_installer.ts
index 958f952fdd5a1..3f165a4a0e219 100644
--- a/src/plugins/home/server/services/sample_data/sample_data_installer.ts
+++ b/src/plugins/home/server/services/sample_data/sample_data_installer.ts
@@ -155,13 +155,13 @@ export class SampleDataInstaller {
private async installDataIndex(dataset: SampleDatasetSchema, dataIndex: DataIndexSchema) {
const index = createIndexName(dataset.id, dataIndex.id);
+
try {
if (dataIndex.isDataStream) {
const request = {
name: index,
body: {
template: {
- settings: { number_of_shards: 1, auto_expand_replicas: '0-1' },
mappings: { properties: dataIndex.fields },
},
index_patterns: [index],
@@ -180,8 +180,6 @@ export class SampleDataInstaller {
settings: {
index: {
...dataIndex.indexSettings,
- number_of_shards: 1,
- auto_expand_replicas: '0-1',
},
},
mappings: { properties: dataIndex.fields },
diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx
index 04379d34a3946..aa941fe395733 100644
--- a/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx
+++ b/src/plugins/unified_search/public/filter_bar/filter_editor/generic_combo_box.tsx
@@ -20,6 +20,7 @@ export interface GenericComboBoxProps {
searchValue: string,
OPTION_CONTENT_CLASSNAME: string
) => React.ReactNode;
+ inputRef?: ((instance: HTMLInputElement | null) => void) | undefined;
[propName: string]: any;
}
diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx
index ae87024bb61a9..0a466c61770ce 100644
--- a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx
+++ b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx
@@ -31,12 +31,8 @@ const COMBOBOX_PADDINGS = 10;
const DEFAULT_FONT = '14px Inter';
class PhraseValueInputUI extends PhraseSuggestorUI {
- comboBoxRef: React.RefObject;
-
- constructor(props: PhraseValueInputProps) {
- super(props);
- this.comboBoxRef = React.createRef();
- }
+ comboBoxWrapperRef = React.createRef();
+ inputRef: HTMLInputElement | null = null;
public render() {
return (
@@ -69,8 +65,11 @@ class PhraseValueInputUI extends PhraseSuggestorUI {
const valueAsStr = String(value);
const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions;
return (
-
+
{
+ this.inputRef = ref;
+ }}
isDisabled={this.props.disabled}
fullWidth={fullWidth}
compressed={this.props.compressed}
@@ -85,7 +84,13 @@ class PhraseValueInputUI extends PhraseSuggestorUI {
options={options}
getLabel={(option) => option}
selectedOptions={value ? [valueAsStr] : []}
- onChange={([newValue = '']) => onChange(newValue)}
+ onChange={([newValue = '']) => {
+ onChange(newValue);
+ setTimeout(() => {
+ // Note: requires a tick skip to correctly blur element focus
+ this.inputRef?.blur();
+ });
+ }}
onSearchChange={this.onSearchChange}
singleSelection={{ asPlainText: true }}
onCreateOption={onChange}
@@ -98,7 +103,7 @@ class PhraseValueInputUI extends PhraseSuggestorUI {
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
defaultFont={DEFAULT_FONT}
comboboxPaddings={COMBOBOX_PADDINGS}
- comboBoxRef={this.comboBoxRef}
+ comboBoxWrapperRef={this.comboBoxWrapperRef}
label={option.label}
search={searchValue}
/>
diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx
index 336849c4ee65a..b31e6aad7d438 100644
--- a/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx
+++ b/src/plugins/unified_search/public/filter_bar/filter_editor/phrases_values_input.tsx
@@ -33,12 +33,7 @@ const COMBOBOX_PADDINGS = 20;
const DEFAULT_FONT = '14px Inter';
class PhrasesValuesInputUI extends PhraseSuggestorUI {
- comboBoxRef: React.RefObject;
-
- constructor(props: PhrasesValuesInputProps) {
- super(props);
- this.comboBoxRef = React.createRef();
- }
+ comboBoxWrapperRef = React.createRef();
public render() {
const { suggestions } = this.state;
@@ -46,7 +41,7 @@ class PhrasesValuesInputUI extends PhraseSuggestorUI {
const options = values ? uniq([...values, ...suggestions]) : suggestions;
return (
-
+
{
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
defaultFont={DEFAULT_FONT}
comboboxPaddings={COMBOBOX_PADDINGS}
- comboBoxRef={this.comboBoxRef}
+ comboBoxWrapperRef={this.comboBoxWrapperRef}
label={option.label}
search={searchValue}
/>
diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx
index 47ea4cbf2c0eb..08236041ab93a 100644
--- a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx
+++ b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.test.tsx
@@ -6,18 +6,16 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { ComponentProps } from 'react';
import { mount } from 'enzyme';
import { TruncatedLabel } from './truncated_label';
describe('truncated_label', () => {
- const defaultProps = {
+ const defaultProps: ComponentProps = {
defaultFont: '14px Inter',
- // jest-canvas-mock mocks measureText as the number of string characters, thats why the width is so low
- width: 30,
defaultComboboxWidth: 130,
comboboxPaddings: 100,
- comboBoxRef: React.createRef(),
+ comboBoxWrapperRef: React.createRef(),
search: '',
label: 'example_field',
};
diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx
index 9f268e46d7929..21304ad244edf 100644
--- a/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx
+++ b/src/plugins/unified_search/public/filter_bar/filter_editor/truncated_label.tsx
@@ -15,7 +15,7 @@ import { throttle } from 'lodash';
interface TruncatedLabelProps {
label: string;
search: string;
- comboBoxRef: RefObject;
+ comboBoxWrapperRef: RefObject;
defaultFont: string;
defaultComboboxWidth: number;
comboboxPaddings: number;
@@ -56,7 +56,7 @@ const truncateLabel = (
export const TruncatedLabel = React.memo(function TruncatedLabel({
label,
- comboBoxRef,
+ comboBoxWrapperRef,
search,
defaultFont,
defaultComboboxWidth,
@@ -69,15 +69,14 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({
width: defaultComboboxWidth - comboboxPaddings,
font: defaultFont,
});
-
const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => {
- if (comboBoxRef.current) {
+ if (comboBoxWrapperRef.current) {
const current = {
...labelProps,
- width: comboBoxRef.current?.clientWidth - comboboxPaddings,
+ width: comboBoxWrapperRef.current.clientWidth - comboboxPaddings,
};
if (shouldRecomputeAll) {
- current.font = window.getComputedStyle(comboBoxRef.current).font;
+ current.font = window.getComputedStyle(comboBoxWrapperRef.current).font;
}
setLabelProps(current);
}
@@ -88,7 +87,7 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({
}, 50);
useEffectOnce(() => {
- if (comboBoxRef.current) {
+ if (comboBoxWrapperRef.current) {
handleResize(undefined, true);
}
diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx
index 3dceef3ef52c9..8c3dc65758c29 100644
--- a/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx
+++ b/src/plugins/unified_search/public/filters_builder/filter_item/field_input.tsx
@@ -43,7 +43,8 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
const { disabled, suggestionsAbstraction } = useContext(FiltersBuilderContextType);
const fields = dataView ? getFilterableFields(dataView) : [];
const id = useGeneratedHtmlId({ prefix: 'fieldInput' });
- const comboBoxRef = useRef(null);
+ const comboBoxWrapperRef = useRef(null);
+ const inputRef = useRef(null);
const onFieldChange = useCallback(
([selectedField]: DataViewField[]) => {
@@ -77,12 +78,25 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
({ label }) => fields[optionFields.findIndex((optionField) => optionField.label === label)]
);
onFieldChange(newValues);
+
+ setTimeout(() => {
+ // Note: requires a tick skip to correctly blur element focus
+ inputRef?.current?.blur();
+ });
+ };
+
+ const handleFocus: React.FocusEventHandler = () => {
+ // Force focus on input due to https://github.com/elastic/eui/issues/7170
+ inputRef?.current?.focus();
};
return (
-
+
{
+ inputRef.current = ref;
+ }}
options={euiOptions}
selectedOptions={selectedEuiOptions}
onChange={onComboBoxChange}
@@ -94,6 +108,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
isClearable={false}
compressed
fullWidth
+ onFocus={handleFocus}
data-test-subj="filterFieldSuggestionList"
renderOption={(option, searchValue) => (
@@ -105,7 +120,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
defaultFont={DEFAULT_FONT}
comboboxPaddings={COMBOBOX_PADDINGS}
- comboBoxRef={comboBoxRef}
+ comboBoxWrapperRef={comboBoxWrapperRef}
label={option.label}
search={searchValue}
/>
diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts
index 5ca888735f5ad..6ec0ac9ab7058 100644
--- a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts
+++ b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.styles.ts
@@ -22,6 +22,13 @@ export const cursorOrCss = css`
export const fieldAndParamCss = (euiTheme: EuiThemeComputed) => css`
min-width: calc(${euiTheme.size.xl} * 5);
+ flex-grow: 1;
+ .euiFormRow {
+ max-width: 800px;
+ }
+ &:focus-within {
+ flex-grow: 4;
+ }
`;
export const operationCss = (euiTheme: EuiThemeComputed) => css`
diff --git a/test/functional/apps/console/_autocomplete.ts b/test/functional/apps/console/_autocomplete.ts
index 8f2a927a4ff24..1944b5ec24761 100644
--- a/test/functional/apps/console/_autocomplete.ts
+++ b/test/functional/apps/console/_autocomplete.ts
@@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('HTTP methods', async () => {
const suggestions = {
G: ['GET'],
- P: ['PUT', 'POST'],
+ P: ['PUT', 'POST', 'PATCH'],
D: ['DELETE'],
H: ['HEAD'],
};
@@ -234,6 +234,8 @@ GET _search
dELETE dELETe dELEtE dELEte dELeTE dELeTe dELetE dELete dElETE dElETe dElEtE dElEte dEleTE dEleTe dEletE dElete
deLETE deLETe deLEtE deLEte deLeTE deLeTe deLetE deLete delETE delETe delEtE delEte deleTE deleTe deletE delete
HEAD HEAd HEaD HEad HeAD HeAd HeaD Head hEAD hEAd hEaD hEad heAD heAd heaD head
+ PATCH PATCh PATcH PATch PAtCH PAtCh PAtcH PAtch PaTCH PaTCh PaTcH PaTch PatCH PatCh PatcH Patch pATCH pATCh pATcH
+ pATch pAtCH pAtCh pAtcH pAtch paTCH paTCh paTcH paTch patCH patCh patcH patch
`.split(/\s+/m)
),
20 // 20 of 112 (approx. one-fifth) should be enough for testing
diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts
index 091ad8ee5a2e8..b8324ce58ce6c 100644
--- a/test/functional/apps/console/_console.ts
+++ b/test/functional/apps/console/_console.ts
@@ -68,6 +68,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(initialSize.width).to.be.greaterThan(afterSize.width);
});
+ it('should return statusCode 400 to unsupported HTTP verbs', async () => {
+ const expectedResponseContains = '"statusCode": 400';
+ await PageObjects.console.enterRequest('\n OPTIONS /');
+ await PageObjects.console.clickPlay();
+ await retry.try(async () => {
+ const actualResponse = await PageObjects.console.getResponse();
+ log.debug(actualResponse);
+ expect(actualResponse).to.contain(expectedResponseContains);
+
+ expect(await PageObjects.console.hasSuccessBadge()).to.be(false);
+ });
+ });
+
describe('with kbn: prefix in request', () => {
before(async () => {
await PageObjects.console.clearTextArea();
diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap
index 2bed81c9c05c3..c389d411462d7 100644
--- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap
+++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap
@@ -1704,7 +1704,7 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the
"total": {
"type": "long",
"_meta": {
- "description": "Total number of shards for span and trasnaction indices"
+ "description": "Total number of shards for span and transaction indices"
}
}
}
@@ -1718,7 +1718,7 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the
"count": {
"type": "long",
"_meta": {
- "description": "Total number of transaction and span documents overall"
+ "description": "Total number of metric documents overall"
}
}
}
@@ -1728,7 +1728,7 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the
"size_in_bytes": {
"type": "long",
"_meta": {
- "description": "Size of the index in byte units overall."
+ "description": "Size of the metric indicess in byte units overall."
}
}
}
diff --git a/x-pack/plugins/apm/dev_docs/telemetry.md b/x-pack/plugins/apm/dev_docs/telemetry.md
index 7bfb065965261..e11ea3bcdec33 100644
--- a/x-pack/plugins/apm/dev_docs/telemetry.md
+++ b/x-pack/plugins/apm/dev_docs/telemetry.md
@@ -69,3 +69,27 @@ mappings snapshot used in the jest tests.
Behavioral telemetry is recorded with the ui_metrics and application_usage methods from the Usage Collection plugin.
Please fill this in with more details.
+
+## Event based telemetry
+
+Event-based telemetry (EBT) allows sending raw or minimally prepared data to the telemetry endpoints.
+
+EBT is part of the core analytics service in Kibana and the `TelemetryService` provides an easy way to track custom events that are specific to `APM`.
+
+#### Collect a new event type
+
+1. You need to define the event type in the [telemetry_events.ts](https://github.com/elastic/kibana/blob/4283802c195231f710be0d9870615fbc31382a31/x-pack/plugins/apm/public/services/telemetry/telemetry_events.ts#L36)
+2. Define the tracking method in the [telemetry_client.ts](https://github.com/elastic/kibana/blob/4283802c195231f710be0d9870615fbc31382a31/x-pack/plugins/apm/public/services/telemetry/telemetry_client.ts#L18)
+3. Use the tracking method with the telemetry client (`telemetry.reportSearchQuerySumbitted({property: test})`)
+
+In addition to the custom properties, analytics module automatically sends context properties. The list of the properties can be found [here](https://docs.elastic.dev/telemetry/collection/event-based-telemetry-context#browser-context)
+
+#### How to check the events
+
+In development, the events are sent to staging telemetry every hour and these events are stored in the `ebt-kibana-browser` dataview.
+
+For instance, you can use a query like the following as an example to filter the apm event Search Query Submitted.
+
+```
+context.applicationId : "apm" and event_type : "Search Query Submitted"
+```
diff --git a/x-pack/plugins/apm/kibana.jsonc b/x-pack/plugins/apm/kibana.jsonc
index 906596709b189..a9329cd965ce7 100644
--- a/x-pack/plugins/apm/kibana.jsonc
+++ b/x-pack/plugins/apm/kibana.jsonc
@@ -15,7 +15,6 @@
"controls",
"embeddable",
"features",
- "infra",
"logsShared",
"inspector",
"licensing",
@@ -41,6 +40,7 @@
"discover",
"fleet",
"fieldFormats",
+ "infra",
"home",
"ml",
"security",
@@ -49,7 +49,7 @@
"usageCollection",
"customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App
"licenseManagement",
- "profiling",
+ "profiling"
],
"requiredBundles": [
"advancedSettings",
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index 6f92ded082a01..a79be6c310be5 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -15,7 +15,7 @@ import {
} from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { ConfigSchema } from '..';
-import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
+import { ApmPluginSetupDeps, ApmPluginStartDeps, ApmServices } from '../plugin';
import { createCallApmApi } from '../services/rest/create_call_apm_api';
import { setHelpExtension } from '../set_help_extension';
import { setReadonlyBadge } from '../update_badge';
@@ -24,7 +24,6 @@ import { ApmAppRoot } from '../components/routing/app_root';
/**
* This module is rendered asynchronously in the Kibana platform.
*/
-
export const renderApp = ({
coreStart,
pluginsSetup,
@@ -32,6 +31,7 @@ export const renderApp = ({
config,
pluginsStart,
observabilityRuleTypeRegistry,
+ apmServices,
}: {
coreStart: CoreStart;
pluginsSetup: ApmPluginSetupDeps;
@@ -39,6 +39,7 @@ export const renderApp = ({
config: ConfigSchema;
pluginsStart: ApmPluginStartDeps;
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
+ apmServices: ApmServices;
}) => {
const { element, theme$ } = appMountParameters;
const apmPluginContextValue = {
@@ -80,6 +81,7 @@ export const renderApp = ({
,
element
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx
index 4b2f8bb39579e..d2e8034cab32f 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx
@@ -40,10 +40,7 @@ export function InstanceActionsMenu({
kuery,
onClose,
}: Props) {
- const {
- core,
- infra: { locators },
- } = useApmPluginContext();
+ const { core, infra } = useApmPluginContext();
const { data, status } = useInstanceDetailsFetcher({
serviceName,
serviceNodeName,
@@ -92,7 +89,7 @@ export function InstanceActionsMenu({
basePath: core.http.basePath,
onFilterByInstanceClick: handleFilterByInstanceClick,
metricsHref,
- infraLocators: locators,
+ infraLocators: infra?.locators,
});
return (
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts
index e3398258b5fed..284e3ca6d4964 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts
@@ -52,67 +52,71 @@ export function getMenuSections({
? new Date(instanceDetails['@timestamp']).valueOf()
: undefined;
const infraMetricsQuery = getInfraMetricsQuery(instanceDetails['@timestamp']);
- const infraNodeLocator = infraLocators.nodeLogsLocator;
+ const infraNodeLocator = infraLocators?.nodeLogsLocator;
- const podActions: Action[] = [
- {
- key: 'podLogs',
- label: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs',
- { defaultMessage: 'Pod logs' }
- ),
- href: infraNodeLocator.getRedirectUrl({
- nodeId: podId!,
- nodeType: 'pod',
- time,
- }),
- condition: !!podId,
- },
- {
- key: 'podMetrics',
- label: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTable.actionMenus.podMetrics',
- { defaultMessage: 'Pod metrics' }
- ),
- href: getInfraHref({
- app: 'metrics',
- basePath,
- path: `/link-to/pod-detail/${podId}`,
- query: infraMetricsQuery,
- }),
- condition: !!podId,
- },
- ];
+ const podActions: Action[] = infraNodeLocator
+ ? [
+ {
+ key: 'podLogs',
+ label: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs',
+ { defaultMessage: 'Pod logs' }
+ ),
+ href: infraNodeLocator?.getRedirectUrl({
+ nodeId: podId!,
+ nodeType: 'pod',
+ time,
+ }),
+ condition: !!podId,
+ },
+ {
+ key: 'podMetrics',
+ label: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTable.actionMenus.podMetrics',
+ { defaultMessage: 'Pod metrics' }
+ ),
+ href: getInfraHref({
+ app: 'metrics',
+ basePath,
+ path: `/link-to/pod-detail/${podId}`,
+ query: infraMetricsQuery,
+ }),
+ condition: !!podId,
+ },
+ ]
+ : [];
- const containerActions: Action[] = [
- {
- key: 'containerLogs',
- label: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs',
- { defaultMessage: 'Container logs' }
- ),
- href: infraNodeLocator.getRedirectUrl({
- nodeId: containerId!,
- nodeType: 'container',
- time,
- }),
- condition: !!containerId,
- },
- {
- key: 'containerMetrics',
- label: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerMetrics',
- { defaultMessage: 'Container metrics' }
- ),
- href: getInfraHref({
- app: 'metrics',
- basePath,
- path: `/link-to/container-detail/${containerId}`,
- query: infraMetricsQuery,
- }),
- condition: !!containerId,
- },
- ];
+ const containerActions: Action[] = infraNodeLocator
+ ? [
+ {
+ key: 'containerLogs',
+ label: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs',
+ { defaultMessage: 'Container logs' }
+ ),
+ href: infraNodeLocator?.getRedirectUrl({
+ nodeId: containerId!,
+ nodeType: 'container',
+ time,
+ }),
+ condition: !!containerId,
+ },
+ {
+ key: 'containerMetrics',
+ label: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerMetrics',
+ { defaultMessage: 'Container metrics' }
+ ),
+ href: getInfraHref({
+ app: 'metrics',
+ basePath,
+ path: `/link-to/container-detail/${containerId}`,
+ query: infraMetricsQuery,
+ }),
+ condition: !!containerId,
+ },
+ ]
+ : [];
const apmActions: Action[] = [
{
diff --git a/x-pack/plugins/apm/public/components/routing/app_root/index.tsx b/x-pack/plugins/apm/public/components/routing/app_root/index.tsx
index a49beaa221566..2af4d21028b80 100644
--- a/x-pack/plugins/apm/public/components/routing/app_root/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/app_root/index.tsx
@@ -32,7 +32,7 @@ import { BreadcrumbsContextProvider } from '../../../context/breadcrumbs/context
import { LicenseProvider } from '../../../context/license/license_context';
import { TimeRangeIdContextProvider } from '../../../context/time_range_id/time_range_id_context';
import { UrlParamsProvider } from '../../../context/url_params_context/url_params_context';
-import { ApmPluginStartDeps } from '../../../plugin';
+import { ApmPluginStartDeps, ApmServices } from '../../../plugin';
import { ApmErrorBoundary } from '../apm_error_boundary';
import { apmRouter } from '../apm_route_config';
import { TrackPageview } from '../track_pageview';
@@ -49,9 +49,11 @@ const storage = new Storage(localStorage);
export function ApmAppRoot({
apmPluginContextValue,
pluginsStart,
+ apmServices,
}: {
apmPluginContextValue: ApmPluginContextValue;
pluginsStart: ApmPluginStartDeps;
+ apmServices: ApmServices;
}) {
const { appMountParameters, core } = apmPluginContextValue;
const { history } = appMountParameters;
@@ -65,7 +67,9 @@ export function ApmAppRoot({
role="main"
>
-
+
{
- const token = session.getTokenAt(
- position.row,
- position.column
- ) as Maybe;
- const tokensInLine = session.getTokens(position.row) as TokenInfo[];
-
- function withWhitespace(
- vals: EQLCodeEditorSuggestion[],
- options: {
- before?: string;
- after?: string;
- } = {}
- ) {
- const { after = ' ' } = options;
- let { before = ' ' } = options;
-
- if (
- before &&
- (token?.value.match(/^\s+$/) || (token && token.type !== 'text'))
- ) {
- before = before.trimLeft();
- }
-
- return vals.map((val) => {
- const suggestion = typeof val === 'string' ? { value: val } : val;
- const valueAsString = suggestion.value;
-
- return {
- ...suggestion,
- caption: valueAsString,
- value: [before, valueAsString, after].join(''),
- };
- });
- }
-
- if (
- position.row === 0 &&
- (!token || token.index === 0) &&
- 'sequence by'.includes(prefix || '')
- ) {
- return withWhitespace(['sequence by'], {
- before: '',
- after: ' ',
- });
- }
-
- const previousTokens = tokensInLine
- .slice(0, token ? tokensInLine.indexOf(token) : tokensInLine.length)
- .reverse();
-
- const completedEqlToken = previousTokens.find((t) =>
- t.type.startsWith('eql.')
- );
-
- switch (completedEqlToken?.type) {
- case undefined:
- return [
- ...withWhitespace(['['], { before: '', after: ' ' }),
- ...(position.row > 2
- ? withWhitespace(['until'], { before: '', after: ' [ ' })
- : []),
- ];
-
- case EQLToken.Sequence:
- return withWhitespace(
- await this.getExternalSuggestions({
- type: EQLCodeEditorSuggestionType.Field,
- }),
- {
- after: '\n\t[ ',
- }
- );
-
- case EQLToken.SequenceItemStart:
- return withWhitespace(
- [
- ...(await this.getExternalSuggestions({
- type: EQLCodeEditorSuggestionType.EventType,
- })),
- 'any',
- ],
- { after: ' where ' }
- );
-
- case EQLToken.EventType:
- return withWhitespace(['where']);
-
- case EQLToken.Where:
- case EQLToken.LogicalOperator:
- return [
- ...withWhitespace(
- await this.getExternalSuggestions({
- type: EQLCodeEditorSuggestionType.Field,
- })
- ),
- ...withWhitespace(['true', 'false'], { after: ' ]\n\t' }),
- ];
-
- case EQLToken.BoolCondition:
- return withWhitespace([']'], { after: '\n\t' });
-
- case EQLToken.Operator:
- case EQLToken.InOperator:
- const field =
- previousTokens?.find((t) => t.type === EQLToken.Field)?.value ?? '';
-
- const hasStartedValueLiteral =
- !!prefix?.trim() || token?.value.trim() === '"';
-
- return withWhitespace(
- await this.getExternalSuggestions({
- type: EQLCodeEditorSuggestionType.Value,
- field,
- value: prefix ?? '',
- }),
- { before: hasStartedValueLiteral ? '' : ' "', after: '" ' }
- );
-
- case EQLToken.Value:
- return [
- ...withWhitespace([']'], { after: '\n\t' }),
- ...withWhitespace(['and', 'or']),
- ];
- }
-
- return [];
- }
-
- private async getExternalSuggestions(
- request: EQLCodeEditorSuggestionRequest
- ): Promise {
- if (this.callback) {
- return this.callback(request);
- }
- return [];
- }
-
- getCompletions(
- _: Editor,
- session: IEditSession,
- position: { row: number; column: number },
- prefix: string | undefined,
- cb: (err: Error | null, suggestions?: EQLCodeEditorSuggestion[]) => void
- ) {
- this.getCompletionsAsync(session, position, prefix)
- .then((suggestions) => {
- cb(
- null,
- suggestions.map((sugg) => {
- const suggestion =
- typeof sugg === 'string'
- ? { value: sugg, score: 1000 }
- : { score: 1000, ...sugg };
-
- return suggestion;
- })
- );
- })
- .catch(cb);
- }
-
- setSuggestionCb(cb?: EQLCodeEditorSuggestionCallback) {
- this.callback = cb;
- }
-}
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts
deleted file mode 100644
index 8d04036451bb0..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts
+++ /dev/null
@@ -1,145 +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 'brace/ext/language_tools';
-import { acequire } from 'brace';
-import { EQLToken } from './tokens';
-
-const TextHighlightRules = acequire(
- 'ace/mode/text_highlight_rules'
-).TextHighlightRules;
-
-export class EQLHighlightRules extends TextHighlightRules {
- constructor() {
- super();
-
- const fieldNameOrValueRegex = /((?:[^\s]+)|(?:".*?"))/;
- const operatorRegex = /(:|==|>|>=|<|<=|!=)/;
-
- const sequenceItemEnd = {
- token: EQLToken.SequenceItemEnd,
- regex: /(\])/,
- next: 'start',
- };
-
- this.$rules = {
- start: [
- {
- token: EQLToken.Sequence,
- regex: /(sequence by)/,
- next: 'field',
- },
- {
- token: EQLToken.SequenceItemStart,
- regex: /(\[)/,
- next: 'sequence_item',
- },
- {
- token: EQLToken.Until,
- regex: /(until)/,
- next: 'start',
- },
- ],
- field: [
- {
- token: EQLToken.Field,
- regex: fieldNameOrValueRegex,
- next: 'start',
- },
- ],
- sequence_item: [
- {
- token: EQLToken.EventType,
- regex: fieldNameOrValueRegex,
- next: 'where',
- },
- ],
- sequence_item_end: [sequenceItemEnd],
- where: [
- {
- token: EQLToken.Where,
- regex: /(where)/,
- next: 'condition',
- },
- ],
- condition: [
- {
- token: EQLToken.BoolCondition,
- regex: /(true|false)/,
- next: 'sequence_item_end',
- },
- {
- token: EQLToken.Field,
- regex: fieldNameOrValueRegex,
- next: 'comparison_operator',
- },
- ],
- comparison_operator: [
- {
- token: EQLToken.Operator,
- regex: operatorRegex,
- next: 'value_or_value_list',
- },
- ],
- value_or_value_list: [
- {
- token: EQLToken.Value,
- regex: /("([^"]+)")|([\d+\.]+)|(true|false|null)/,
- next: 'logical_operator_or_sequence_item_end',
- },
- {
- token: EQLToken.InOperator,
- regex: /(in)/,
- next: 'value_list',
- },
- ],
- logical_operator_or_sequence_item_end: [
- {
- token: EQLToken.LogicalOperator,
- regex: /(and|or|not)/,
- next: 'condition',
- },
- sequenceItemEnd,
- ],
- value_list: [
- {
- token: EQLToken.ValueListStart,
- regex: /(\()/,
- next: 'value_list_item',
- },
- ],
- value_list_item: [
- {
- token: EQLToken.Value,
- regex: fieldNameOrValueRegex,
- next: 'comma',
- },
- ],
- comma: [
- {
- token: EQLToken.Comma,
- regex: /,/,
- next: 'value_list_item_or_end',
- },
- ],
- value_list_item_or_end: [
- {
- token: EQLToken.Value,
- regex: fieldNameOrValueRegex,
- next: 'comma',
- },
- {
- token: EQLToken.ValueListEnd,
- regex: /\)/,
- next: 'logical_operator_or_sequence_item_end',
- },
- ],
- };
-
- this.normalizeRules();
- }
-}
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts
deleted file mode 100644
index 36f923b3210c0..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { TextMode as TextModeInterface, acequire } from 'brace';
-import { EQL_MODE_NAME } from './constants';
-import { EQLHighlightRules } from './eql_highlight_rules';
-
-type ITextMode = new () => TextModeInterface;
-
-const TextMode = acequire('ace/mode/text').Mode as ITextMode;
-
-export class EQLMode extends TextMode {
- HighlightRules: typeof EQLHighlightRules;
- $id: string;
- constructor() {
- super();
- this.$id = EQL_MODE_NAME;
- this.HighlightRules = EQLHighlightRules;
- }
-}
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx b/x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx
deleted file mode 100644
index 8a8cd1b0dff41..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx
+++ /dev/null
@@ -1,54 +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 'brace/ext/language_tools';
-import { last } from 'lodash';
-import React, { useRef } from 'react';
-import { EuiCodeEditor } from '@kbn/es-ui-shared-plugin/public';
-import { EQLCodeEditorCompleter } from './completer';
-import { EQL_THEME_NAME } from './constants';
-import { EQLMode } from './eql_mode';
-import './theme';
-import { EQLCodeEditorProps } from './types';
-
-export function EQLCodeEditor(props: EQLCodeEditorProps) {
- const {
- showGutter = false,
- setOptions,
- getSuggestions,
- ...restProps
- } = props;
-
- const completer = useRef(new EQLCodeEditorCompleter());
- const eqlMode = useRef(new EQLMode());
-
- completer.current.setSuggestionCb(getSuggestions);
-
- const options = {
- enableBasicAutocompletion: true,
- enableLiveAutocompletion: true,
- wrap: true,
- ...setOptions,
- };
-
- return (
-
- {
- if (editor) {
- editor.editor.completers = [completer.current];
- }
- }}
- {...restProps}
- />
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx b/x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx
deleted file mode 100644
index 3432331aaa062..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx
+++ /dev/null
@@ -1,39 +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 { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
-import { once } from 'lodash';
-import React, { useEffect, useState } from 'react';
-import { EQLCodeEditorProps } from './types';
-
-const loadEqlCodeEditor = once(() => import('.').then((m) => m.EQLCodeEditor));
-
-type EQLCodeEditorComponentType = Awaited>;
-
-export function LazilyLoadedEQLCodeEditor(props: EQLCodeEditorProps) {
- const [EQLCodeEditor, setEQLCodeEditor] = useState<
- EQLCodeEditorComponentType | undefined
- >();
-
- useEffect(() => {
- loadEqlCodeEditor().then((editor) => {
- setEQLCodeEditor(() => {
- return editor;
- });
- });
- }, []);
-
- return EQLCodeEditor ? (
-
- ) : (
-
-
-
-
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts
deleted file mode 100644
index 2dfbecf63f428..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { euiLightVars as theme } from '@kbn/ui-theme';
-import { EQL_THEME_NAME } from './constants';
-
-// @ts-expect-error
-ace.define(
- EQL_THEME_NAME,
- ['require', 'exports', 'module', 'ace/lib/dom'],
- function (acequire: any, exports: any) {
- exports.isDark = false;
- exports.cssClass = 'ace-eql';
- exports.cssText = `
- .ace-eql .ace_scroller {
- background-color: transparent;
- }
- .ace-eql .ace_marker-layer .ace_selection {
- background: rgb(181, 213, 255);
- }
- .ace-eql .ace_placeholder {
- color: ${theme.euiTextSubduedColor};
- padding: 0;
- }
- .ace-eql .ace_sequence,
- .ace-eql .ace_where,
- .ace-eql .ace_until {
- color: ${theme.euiColorDarkShade};
- }
- .ace-eql .ace_sequence_item_start,
- .ace-eql .ace_sequence_item_end,
- .ace-eql .ace_operator,
- .ace-eql .ace_logical_operator {
- color: ${theme.euiColorMediumShade};
- }
- .ace-eql .ace_value,
- .ace-eql .ace_bool_condition {
- color: ${theme.euiColorAccent};
- }
- .ace-eql .ace_event_type,
- .ace-eql .ace_field {
- color: ${theme.euiColorPrimaryText};
- }
- // .ace-eql .ace_gutter {
- // color: #333;
- // }
- .ace-eql .ace_print-margin {
- width: 1px;
- background: #e8e8e8;
- }
- .ace-eql .ace_fold {
- background-color: #6B72E6;
- }
- .ace-eql .ace_cursor {
- color: black;
- }
- .ace-eql .ace_invisible {
- color: rgb(191, 191, 191);
- }
- .ace-eql .ace_marker-layer .ace_selection {
- background: rgb(181, 213, 255);
- }
- .ace-eql.ace_multiselect .ace_selection.ace_start {
- box-shadow: 0 0 3px 0px white;
- }
- .ace-eql .ace_marker-layer .ace_step {
- background: rgb(252, 255, 0);
- }
- .ace-eql .ace_marker-layer .ace_stack {
- background: rgb(164, 229, 101);
- }
- .ace-eql .ace_marker-layer .ace_bracket {
- margin: -1px 0 0 -1px;
- border: 1px solid rgb(192, 192, 192);
- }
- .ace-eql .ace_marker-layer .ace_selected-word {
- background: rgb(250, 250, 255);
- border: 1px solid rgb(200, 200, 250);
- }
- .ace-eql .ace_indent-guide {
- background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
- }`;
-
- const dom = acequire('../lib/dom');
- dom.importCssString(exports.cssText, exports.cssClass);
- }
-);
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts
deleted file mode 100644
index 5525d8318afaf..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts
+++ /dev/null
@@ -1,25 +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.
- */
-
-export enum EQLToken {
- Sequence = 'eql.sequence',
- SequenceItemStart = 'eql.sequence_item_start',
- SequenceItemEnd = 'eql.sequence_item_end',
- Until = 'eql.until',
- Field = 'eql.field',
- EventType = 'eql.event_type',
- Where = 'eql.where',
- BoolCondition = 'eql.bool_condition',
- Operator = 'eql.operator',
- Value = 'eql.value',
- LogicalOperator = 'eql.logical_operator',
- InOperator = 'eql.in_operator',
- ValueListStart = 'eql.value_list_start',
- ValueListItem = 'eql.value_list_item',
- ValueListEnd = 'eql.value_list_end',
- Comma = 'eql.comma',
-}
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts
deleted file mode 100644
index 250cda155ea18..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts
+++ /dev/null
@@ -1,33 +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 { EuiCodeEditorProps } from '@kbn/es-ui-shared-plugin/public';
-import { EQLCodeEditorSuggestionType } from './constants';
-
-export type EQLCodeEditorSuggestion =
- | string
- | { value: string; score?: number };
-
-export type EQLCodeEditorSuggestionRequest =
- | {
- type:
- | EQLCodeEditorSuggestionType.EventType
- | EQLCodeEditorSuggestionType.Field;
- }
- | { type: EQLCodeEditorSuggestionType.Value; field: string; value: string };
-
-export type EQLCodeEditorSuggestionCallback = (
- request: EQLCodeEditorSuggestionRequest
-) => Promise;
-
-export type EQLCodeEditorProps = Omit<
- EuiCodeEditorProps,
- 'mode' | 'theme' | 'setOptions'
-> & {
- getSuggestions?: EQLCodeEditorSuggestionCallback;
- setOptions?: EuiCodeEditorProps['setOptions'];
-};
diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts
index 9b50bdd350a3f..8f230ba94abe8 100644
--- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts
+++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts
@@ -49,7 +49,7 @@ export const getSections = ({
basePath: IBasePath;
location: Location;
apmRouter: ApmRouter;
- infraLocators: InfraLocators;
+ infraLocators?: InfraLocators;
infraLinksAvailable: boolean;
profilingLocators?: ProfilingLocators;
rangeFrom: string;
@@ -60,7 +60,7 @@ export const getSections = ({
const hostName = transaction.host?.hostname;
const podId = transaction.kubernetes?.pod?.uid;
const containerId = transaction.container?.id;
- const { nodeLogsLocator, logsLocator } = infraLocators;
+ const { nodeLogsLocator, logsLocator } = infraLocators ?? {};
const time = Math.round(transaction.timestamp.us / 1000);
const infraMetricsQuery = getInfraMetricsQuery(transaction);
@@ -79,94 +79,102 @@ export const getSections = ({
)}`,
});
- const podActions: Action[] = [
- {
- key: 'podLogs',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel',
- { defaultMessage: 'Pod logs' }
- ),
- href: nodeLogsLocator.getRedirectUrl({
- nodeId: podId!,
- nodeType: 'pod',
- time,
- }),
- condition: !!podId,
- },
- {
- key: 'podMetrics',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel',
- { defaultMessage: 'Pod metrics' }
- ),
- href: getInfraHref({
- app: 'metrics',
- basePath,
- path: `/link-to/pod-detail/${podId}`,
- query: infraMetricsQuery,
- }),
- condition: !!podId,
- },
- ];
+ const podActions: Action[] = nodeLogsLocator
+ ? [
+ {
+ key: 'podLogs',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel',
+ { defaultMessage: 'Pod logs' }
+ ),
+ href: nodeLogsLocator.getRedirectUrl({
+ nodeId: podId!,
+ nodeType: 'pod',
+ time,
+ }),
+ condition: !!podId,
+ },
+ {
+ key: 'podMetrics',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel',
+ { defaultMessage: 'Pod metrics' }
+ ),
+ href: getInfraHref({
+ app: 'metrics',
+ basePath,
+ path: `/link-to/pod-detail/${podId}`,
+ query: infraMetricsQuery,
+ }),
+ condition: !!podId,
+ },
+ ]
+ : [];
- const containerActions: Action[] = [
- {
- key: 'containerLogs',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel',
- { defaultMessage: 'Container logs' }
- ),
- href: nodeLogsLocator.getRedirectUrl({
- nodeId: containerId!,
- nodeType: 'container',
- time,
- }),
- condition: !!containerId,
- },
- {
- key: 'containerMetrics',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel',
- { defaultMessage: 'Container metrics' }
- ),
- href: getInfraHref({
- app: 'metrics',
- basePath,
- path: `/link-to/container-detail/${containerId}`,
- query: infraMetricsQuery,
- }),
- condition: !!containerId,
- },
- ];
+ const containerActions: Action[] = nodeLogsLocator
+ ? [
+ {
+ key: 'containerLogs',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel',
+ { defaultMessage: 'Container logs' }
+ ),
+ href: nodeLogsLocator.getRedirectUrl({
+ nodeId: containerId!,
+ nodeType: 'container',
+ time,
+ }),
+ condition: !!containerId,
+ },
+ {
+ key: 'containerMetrics',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel',
+ { defaultMessage: 'Container metrics' }
+ ),
+ href: getInfraHref({
+ app: 'metrics',
+ basePath,
+ path: `/link-to/container-detail/${containerId}`,
+ query: infraMetricsQuery,
+ }),
+ condition: !!containerId,
+ },
+ ]
+ : [];
const hostActions: Action[] = [
- {
- key: 'hostLogs',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel',
- { defaultMessage: 'Host logs' }
- ),
- href: nodeLogsLocator.getRedirectUrl({
- nodeId: hostName!,
- nodeType: 'host',
- time,
- }),
- condition: !!hostName,
- },
- {
- key: 'hostMetrics',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel',
- { defaultMessage: 'Host metrics' }
- ),
- href: getInfraHref({
- app: 'metrics',
- basePath,
- path: `/link-to/host-detail/${hostName}`,
- query: infraMetricsQuery,
- }),
- condition: !!hostName,
- },
+ ...(nodeLogsLocator
+ ? [
+ {
+ key: 'hostLogs',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel',
+ { defaultMessage: 'Host logs' }
+ ),
+ href: nodeLogsLocator.getRedirectUrl({
+ nodeId: hostName!,
+ nodeType: 'host',
+ time,
+ }),
+ condition: !!hostName,
+ },
+ {
+ key: 'hostMetrics',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel',
+ { defaultMessage: 'Host metrics' }
+ ),
+ href: getInfraHref({
+ app: 'metrics',
+ basePath,
+ path: `/link-to/host-detail/${hostName}`,
+ query: infraMetricsQuery,
+ }),
+ condition: !!hostName,
+ },
+ ]
+ : []),
{
key: 'hostProfilingFlamegraph',
label: i18n.translate(
@@ -205,20 +213,22 @@ export const getSections = ({
},
];
- const logActions: Action[] = [
- {
- key: 'traceLogs',
- label: i18n.translate(
- 'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel',
- { defaultMessage: 'Trace logs' }
- ),
- href: logsLocator.getRedirectUrl({
- filter: `trace.id:"${transaction.trace.id}" OR (not trace.id:* AND "${transaction.trace.id}")`,
- time,
- }),
- condition: true,
- },
- ];
+ const logActions: Action[] = logsLocator
+ ? [
+ {
+ key: 'traceLogs',
+ label: i18n.translate(
+ 'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel',
+ { defaultMessage: 'Trace logs' }
+ ),
+ href: logsLocator.getRedirectUrl({
+ filter: `trace.id:"${transaction.trace.id}" OR (not trace.id:* AND "${transaction.trace.id}")`,
+ time,
+ }),
+ condition: true,
+ },
+ ]
+ : [];
const uptimeActions: Action[] = [
{
@@ -273,7 +283,7 @@ export const getSections = ({
const sectionRecord: SectionRecord = {
observability: [
- ...(infraLinksAvailable
+ ...(infraLinksAvailable && infraLocators
? [
{
key: 'podDetails',
@@ -329,19 +339,26 @@ export const getSections = ({
]
: []),
- {
- key: 'traceDetails',
- title: i18n.translate('xpack.apm.transactionActionMenu.trace.title', {
- defaultMessage: 'Trace details',
- }),
- subtitle: i18n.translate(
- 'xpack.apm.transactionActionMenu.trace.subtitle',
- {
- defaultMessage: 'View trace logs to get further details.',
- }
- ),
- actions: logActions,
- },
+ ...(infraLocators
+ ? [
+ {
+ key: 'traceDetails',
+ title: i18n.translate(
+ 'xpack.apm.transactionActionMenu.trace.title',
+ {
+ defaultMessage: 'Trace details',
+ }
+ ),
+ subtitle: i18n.translate(
+ 'xpack.apm.transactionActionMenu.trace.subtitle',
+ {
+ defaultMessage: 'View trace logs to get further details.',
+ }
+ ),
+ actions: logActions,
+ },
+ ]
+ : []),
{
key: 'statusDetails',
title: i18n.translate('xpack.apm.transactionActionMenu.status.title', {
diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx
index e888cec54f11a..14cae53ba28cf 100644
--- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx
+++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx
@@ -125,11 +125,7 @@ function ActionMenuSections({
transaction?: Transaction;
profilingLocators?: ProfilingLocators;
}) {
- const {
- core,
- uiActions,
- infra: { locators },
- } = useApmPluginContext();
+ const { core, uiActions, infra } = useApmPluginContext();
const location = useLocation();
const apmRouter = useApmRouter();
@@ -151,7 +147,7 @@ function ActionMenuSections({
basePath: core.http.basePath,
location,
apmRouter,
- infraLocators: locators,
+ infraLocators: infra?.locators,
infraLinksAvailable,
profilingLocators,
rangeFrom,
diff --git a/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx
index a04573a2d743e..66e7bfd514d86 100644
--- a/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx
@@ -22,7 +22,7 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWith
import { OnRefreshChangeProps } from '@elastic/eui/src/components/date_picker/types';
import { UIProcessorEvent } from '../../../../common/processor_event';
import { TimePickerTimeDefaults } from '../date_picker/typings';
-import { ApmPluginStartDeps } from '../../../plugin';
+import { ApmPluginStartDeps, ApmServices } from '../../../plugin';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useApmDataView } from '../../../hooks/use_apm_data_view';
import { useProcessorEvent } from '../../../hooks/use_processor_event';
@@ -36,6 +36,8 @@ import {
toBoolean,
toNumber,
} from '../../../context/url_params_context/helpers';
+import { getKueryFields } from '../../../../common/utils/get_kuery_fields';
+import { SearchQueryActions } from '../../../services/telemetry';
export const DEFAULT_REFRESH_INTERVAL = 60000;
@@ -138,11 +140,12 @@ export function UnifiedSearchBar({
},
core,
} = useApmPluginContext();
- const { services } = useKibana();
+ const { services } = useKibana();
const {
data: {
query: { queryString: queryStringService, timefilter: timeFilterService },
},
+ telemetry,
} = services;
const {
@@ -241,6 +244,7 @@ export function UnifiedSearchBar({
payload: { dateRange: TimeRange; query?: Query },
isUpdate?: boolean
) => {
+ let action = SearchQueryActions.Submit;
if (dataView == null) {
return;
}
@@ -256,6 +260,9 @@ export function UnifiedSearchBar({
if (!res) {
return;
}
+ const kueryFields = getKueryFields([
+ fromKueryExpression(query?.query as string),
+ ]);
const existingQueryParams = toQuery(location.search);
const updatedQueryWithTime = {
@@ -274,8 +281,14 @@ export function UnifiedSearchBar({
search: fromQuery(newSearchParams),
});
} else {
+ action = SearchQueryActions.Refresh;
onRefresh();
}
+ telemetry.reportSearchQuerySubmitted({
+ kueryFields,
+ action,
+ timerange: `${rangeFrom} - ${rangeTo}`,
+ });
} catch (e) {
console.log('Invalid kuery syntax'); // eslint-disable-line no-console
}
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index 2447892d09e33..800fb6bf123cd 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -82,7 +82,7 @@ import { getLazyAPMPolicyCreateExtension } from './components/fleet_integration/
import { getLazyAPMPolicyEditExtension } from './components/fleet_integration/lazy_apm_policy_edit_extension';
import { featureCatalogueEntry } from './feature_catalogue_entry';
import { APMServiceDetailLocator } from './locator/service_detail_locator';
-
+import { ITelemetryClient, TelemetryService } from './services/telemetry';
export type ApmPluginSetup = ReturnType;
export type ApmPluginStart = void;
@@ -106,6 +106,10 @@ export interface ApmPluginSetupDeps {
profiling?: ProfilingPluginSetup;
}
+export interface ApmServices {
+ telemetry: ITelemetryClient;
+}
+
export interface ApmPluginStartDeps {
alerting?: AlertingPluginPublicStart;
charts?: ChartsPluginStart;
@@ -124,7 +128,7 @@ export interface ApmPluginStartDeps {
fieldFormats?: FieldFormatsStart;
security?: SecurityPluginStart;
spaces?: SpacesPluginStart;
- infra: InfraClientStartExports;
+ infra?: InfraClientStartExports;
dataViews: DataViewsPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
storage: IStorageWrapper;
@@ -181,16 +185,17 @@ const apmTutorialTitle = i18n.translate(
);
export class ApmPlugin implements Plugin {
+ private telemetry: TelemetryService;
constructor(
private readonly initializerContext: PluginInitializerContext
) {
this.initializerContext = initializerContext;
+ this.telemetry = new TelemetryService();
}
public setup(core: CoreSetup, plugins: ApmPluginSetupDeps) {
const config = this.initializerContext.config.get();
const pluginSetupDeps = plugins;
-
const { featureFlags } = config;
if (pluginSetupDeps.home) {
@@ -273,6 +278,8 @@ export class ApmPlugin implements Plugin {
};
};
+ this.telemetry.setup({ analytics: core.analytics });
+
// Registers a status check callback for the tutorial to call and verify if the APM integration is installed on fleet.
pluginSetupDeps.home?.tutorials.registerCustomStatusCheck(
'apm_fleet_server_status_check',
@@ -332,6 +339,9 @@ export class ApmPlugin implements Plugin {
const { observabilityRuleTypeRegistry } = plugins.observability;
+ // Register APM telemetry based events
+ const telemetry = this.telemetry.start();
+
core.application.register({
id: 'apm',
title: 'APM',
@@ -388,7 +398,6 @@ export class ApmPlugin implements Plugin {
import('./application'),
core.getStartServices(),
]);
-
return renderApp({
coreStart,
pluginsSetup: pluginSetupDeps,
@@ -396,6 +405,9 @@ export class ApmPlugin implements Plugin {
config,
pluginsStart: pluginsStart as ApmPluginStartDeps,
observabilityRuleTypeRegistry,
+ apmServices: {
+ telemetry,
+ },
});
},
});
diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts b/x-pack/plugins/apm/public/services/telemetry/index.ts
similarity index 55%
rename from x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts
rename to x-pack/plugins/apm/public/services/telemetry/index.ts
index 5fe28cb602c49..c7cc9eb577e38 100644
--- a/x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts
+++ b/x-pack/plugins/apm/public/services/telemetry/index.ts
@@ -5,11 +5,6 @@
* 2.0.
*/
-export const EQL_MODE_NAME = 'ace/mode/eql';
-export const EQL_THEME_NAME = 'ace/theme/eql';
-
-export enum EQLCodeEditorSuggestionType {
- EventType = 'eventType',
- Field = 'field',
- Value = 'value',
-}
+export * from './telemetry_client';
+export * from './telemetry_service';
+export * from './types';
diff --git a/x-pack/plugins/apm/public/services/telemetry/telemetry_client.ts b/x-pack/plugins/apm/public/services/telemetry/telemetry_client.ts
new file mode 100644
index 0000000000000..a462acaff24f8
--- /dev/null
+++ b/x-pack/plugins/apm/public/services/telemetry/telemetry_client.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 { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
+import {
+ TelemetryEventTypes,
+ ITelemetryClient,
+ SearchQuerySubmittedParams,
+} from './types';
+
+export class TelemetryClient implements ITelemetryClient {
+ constructor(private analytics: AnalyticsServiceSetup) {}
+
+ public reportSearchQuerySubmitted = ({
+ kueryFields,
+ timerange,
+ action,
+ }: SearchQuerySubmittedParams) => {
+ this.analytics.reportEvent(TelemetryEventTypes.SEARCH_QUERY_SUBMITTED, {
+ kueryFields,
+ timerange,
+ action,
+ });
+ };
+}
diff --git a/x-pack/plugins/apm/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/apm/public/services/telemetry/telemetry_events.ts
new file mode 100644
index 0000000000000..e4f4878342133
--- /dev/null
+++ b/x-pack/plugins/apm/public/services/telemetry/telemetry_events.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { TelemetryEventTypes, TelemetryEvent } from './types';
+
+const searchQuerySubmittedEventType: TelemetryEvent = {
+ eventType: TelemetryEventTypes.SEARCH_QUERY_SUBMITTED,
+ schema: {
+ kueryFields: {
+ type: 'array',
+ items: {
+ type: 'text',
+ _meta: {
+ description: 'The kuery fields used in the search',
+ },
+ },
+ },
+ timerange: {
+ type: 'text',
+ _meta: {
+ description: 'The timerange of the search',
+ },
+ },
+ action: {
+ type: 'keyword',
+ _meta: {
+ description: 'The action performed (e.g., submit, refresh)',
+ },
+ },
+ },
+};
+
+export const apmTelemetryEventBasedTypes = [searchQuerySubmittedEventType];
diff --git a/x-pack/plugins/apm/public/services/telemetry/telemetry_service.test.ts b/x-pack/plugins/apm/public/services/telemetry/telemetry_service.test.ts
new file mode 100644
index 0000000000000..71068a042f6f1
--- /dev/null
+++ b/x-pack/plugins/apm/public/services/telemetry/telemetry_service.test.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { coreMock } from '@kbn/core/server/mocks';
+import { apmTelemetryEventBasedTypes } from './telemetry_events';
+import { TelemetryService } from './telemetry_service';
+import { SearchQueryActions, TelemetryEventTypes } from './types';
+
+describe('TelemetryService', () => {
+ const service = new TelemetryService();
+
+ const mockCoreStart = coreMock.createSetup();
+ service.setup({ analytics: mockCoreStart.analytics });
+
+ it('should register all events', () => {
+ expect(mockCoreStart.analytics.registerEventType).toHaveBeenCalledTimes(
+ apmTelemetryEventBasedTypes.length
+ );
+ });
+
+ it('should report search query event with the properties', async () => {
+ const telemetry = service.start();
+
+ telemetry.reportSearchQuerySubmitted({
+ kueryFields: ['service.name', 'span.id'],
+ action: SearchQueryActions.Submit,
+ timerange: 'now-15h-now',
+ });
+
+ expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledTimes(1);
+ expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledWith(
+ TelemetryEventTypes.SEARCH_QUERY_SUBMITTED,
+ {
+ kueryFields: ['service.name', 'span.id'],
+ action: SearchQueryActions.Submit,
+ timerange: 'now-15h-now',
+ }
+ );
+ });
+});
diff --git a/x-pack/plugins/apm/public/services/telemetry/telemetry_service.ts b/x-pack/plugins/apm/public/services/telemetry/telemetry_service.ts
new file mode 100644
index 0000000000000..7ff4228eb821c
--- /dev/null
+++ b/x-pack/plugins/apm/public/services/telemetry/telemetry_service.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
+import {
+ TelemetryServiceSetupParams,
+ ITelemetryClient,
+ TelemetryEventParams,
+} from './types';
+import { apmTelemetryEventBasedTypes } from './telemetry_events';
+import { TelemetryClient } from './telemetry_client';
+
+/**
+ * Service that interacts with the Core's analytics module
+ */
+export class TelemetryService {
+ constructor(private analytics: AnalyticsServiceSetup | null = null) {}
+
+ public setup({ analytics }: TelemetryServiceSetupParams) {
+ this.analytics = analytics;
+
+ apmTelemetryEventBasedTypes.forEach((eventConfig) =>
+ analytics.registerEventType(eventConfig)
+ );
+ }
+
+ public start(): ITelemetryClient {
+ if (!this.analytics) {
+ throw new Error(
+ 'The TelemetryService.setup() method has not been invoked, be sure to call it during the plugin setup.'
+ );
+ }
+
+ return new TelemetryClient(this.analytics);
+ }
+}
diff --git a/x-pack/plugins/apm/public/services/telemetry/types.ts b/x-pack/plugins/apm/public/services/telemetry/types.ts
new file mode 100644
index 0000000000000..11a8a6f225f27
--- /dev/null
+++ b/x-pack/plugins/apm/public/services/telemetry/types.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { RootSchema } from '@kbn/analytics-client';
+import type { AnalyticsServiceSetup } from '@kbn/core/public';
+
+export interface TelemetryServiceSetupParams {
+ analytics: AnalyticsServiceSetup;
+}
+
+export enum SearchQueryActions {
+ Submit = 'submit',
+ Refresh = 'refresh',
+}
+export interface SearchQuerySubmittedParams {
+ kueryFields: string[];
+ timerange: string;
+ action: SearchQueryActions;
+}
+
+export type TelemetryEventParams = SearchQuerySubmittedParams;
+
+export interface ITelemetryClient {
+ reportSearchQuerySubmitted(params: SearchQuerySubmittedParams): void;
+}
+
+export enum TelemetryEventTypes {
+ SEARCH_QUERY_SUBMITTED = 'Search Query Submitted',
+}
+
+export interface TelemetryEvent {
+ eventType: TelemetryEventTypes.SEARCH_QUERY_SUBMITTED;
+ schema: RootSchema;
+}
diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts
index b5d361777b602..e43fda17a951c 100644
--- a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts
+++ b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts
@@ -931,7 +931,7 @@ export const apmSchema: MakeSchemaFrom = {
type: 'long',
_meta: {
description:
- 'Total number of shards for span and trasnaction indices',
+ 'Total number of shards for span and transaction indices',
},
},
},
@@ -941,8 +941,7 @@ export const apmSchema: MakeSchemaFrom = {
count: {
type: 'long',
_meta: {
- description:
- 'Total number of transaction and span documents overall',
+ description: 'Total number of metric documents overall',
},
},
},
@@ -950,7 +949,8 @@ export const apmSchema: MakeSchemaFrom = {
size_in_bytes: {
type: 'long',
_meta: {
- description: 'Size of the index in byte units overall.',
+ description:
+ 'Size of the metric indicess in byte units overall.',
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts b/x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts
index aec76a75fb944..24b76edb4d887 100644
--- a/x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts
@@ -15,6 +15,9 @@ export async function getInfraMetricIndices({
infraPlugin: Required;
savedObjectsClient: SavedObjectsClientContract;
}): Promise {
+ if (!infraPlugin) {
+ throw new Error('Infra Plugin needs to be setup');
+ }
const infra = await infraPlugin.start();
const infraMetricIndices = await infra.getMetricIndices(savedObjectsClient);
diff --git a/x-pack/plugins/apm/server/routes/infrastructure/route.ts b/x-pack/plugins/apm/server/routes/infrastructure/route.ts
index 151ed589396ce..4117a43ce1e3f 100644
--- a/x-pack/plugins/apm/server/routes/infrastructure/route.ts
+++ b/x-pack/plugins/apm/server/routes/infrastructure/route.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
import * as t from 'io-ts';
+import Boom from '@hapi/boom';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { getApmEventClient } from '../../lib/helpers/get_apm_event_client';
import { environmentRt, kueryRt, rangeRt } from '../default_api_types';
@@ -29,9 +30,12 @@ const infrastructureRoute = createApmServerRoute({
hostNames: string[];
podNames: string[];
}> => {
+ if (!resources.plugins.infra) {
+ throw Boom.notFound();
+ }
+
const apmEventClient = await getApmEventClient(resources);
const infraMetricsClient = createInfraMetricsClient(resources);
-
const { params } = resources;
const {
diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts
index e13c7564a3fff..4ac0a37b3d10d 100644
--- a/x-pack/plugins/apm/server/routes/services/route.ts
+++ b/x-pack/plugins/apm/server/routes/services/route.ts
@@ -239,7 +239,6 @@ const serviceMetadataDetailsRoute = createApmServerRoute({
options: { tags: ['access:apm'] },
handler: async (resources): Promise => {
const apmEventClient = await getApmEventClient(resources);
- const infraMetricsClient = createInfraMetricsClient(resources);
const { params } = resources;
const { serviceName } = params.path;
const { start, end } = params.query;
@@ -251,7 +250,8 @@ const serviceMetadataDetailsRoute = createApmServerRoute({
end,
});
- if (serviceMetadataDetails?.container?.ids) {
+ if (serviceMetadataDetails?.container?.ids && resources.plugins.infra) {
+ const infraMetricsClient = createInfraMetricsClient(resources);
const containerMetadata = await getServiceOverviewContainerMetadata({
infraMetricsClient,
containerIds: serviceMetadataDetails.container.ids,
@@ -748,7 +748,6 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({
(ServiceInstanceContainerMetadataDetails | {})
> => {
const apmEventClient = await getApmEventClient(resources);
- const infraMetricsClient = createInfraMetricsClient(resources);
const { params } = resources;
const { serviceName, serviceNodeName } = params.path;
const { start, end } = params.query;
@@ -762,7 +761,11 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({
end,
});
- if (serviceInstanceMetadataDetails?.container?.id) {
+ if (
+ serviceInstanceMetadataDetails?.container?.id &&
+ resources.plugins.infra
+ ) {
+ const infraMetricsClient = createInfraMetricsClient(resources);
const containerMetadata = await getServiceInstanceContainerMetadata({
infraMetricsClient,
containerId: serviceInstanceMetadataDetails.container.id,
diff --git a/x-pack/plugins/apm/server/types.ts b/x-pack/plugins/apm/server/types.ts
index 054861a082b44..ec3fb5b80e130 100644
--- a/x-pack/plugins/apm/server/types.ts
+++ b/x-pack/plugins/apm/server/types.ts
@@ -75,7 +75,7 @@ export interface APMPluginSetupDependencies {
licensing: LicensingPluginSetup;
observability: ObservabilityPluginSetup;
ruleRegistry: RuleRegistryPluginSetupContract;
- infra: InfraPluginSetup;
+ infra?: InfraPluginSetup;
dataViews: {};
share: SharePluginSetup;
diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json
index a9b8c332426c5..fe805744ad11d 100644
--- a/x-pack/plugins/apm/tsconfig.json
+++ b/x-pack/plugins/apm/tsconfig.json
@@ -61,7 +61,6 @@
"@kbn/rule-data-utils",
"@kbn/core-lifecycle-browser",
"@kbn/shared-ux-page-kibana-template",
- "@kbn/es-ui-shared-plugin",
"@kbn/es-types",
"@kbn/analytics",
"@kbn/rison",
@@ -97,6 +96,8 @@
"@kbn/discover-plugin",
"@kbn/observability-ai-assistant-plugin",
"@kbn/apm-data-access-plugin",
+ "@kbn/core-analytics-server",
+ "@kbn/analytics-client",
"@kbn/monaco"
],
"exclude": ["target/**/*"]
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.test.tsx
new file mode 100644
index 0000000000000..215860c9d475f
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.test.tsx
@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+
+import type { AgentPolicy, PackagePolicy } from '../../../../../../common/types';
+
+import { createFleetTestRendererMock } from '../../../../../mock';
+
+import { AgentPolicyActionMenu } from './actions_menu';
+
+describe('AgentPolicyActionMenu', () => {
+ const baseAgentPolicy: AgentPolicy = {
+ id: 'test',
+ is_managed: false,
+ is_protected: false,
+ name: 'test-agent-policy',
+ namespace: 'default',
+ package_policies: [] as PackagePolicy[],
+ revision: 1,
+ status: 'active',
+ updated_at: new Date().toISOString(),
+ updated_by: 'test',
+ };
+
+ describe('delete action', () => {
+ it('is enabled when a managed package policy is not present', () => {
+ const testRenderer = createFleetTestRendererMock();
+ const agentPolicyWithStandardPackagePolicy: AgentPolicy = {
+ ...baseAgentPolicy,
+ package_policies: [
+ {
+ id: 'test-package-policy',
+ is_managed: false,
+ created_at: new Date().toISOString(),
+ created_by: 'test',
+ enabled: true,
+ inputs: [],
+ name: 'test-package-policy',
+ namespace: 'default',
+ policy_id: 'test',
+ revision: 1,
+ updated_at: new Date().toISOString(),
+ updated_by: 'test',
+ },
+ ],
+ };
+
+ const result = testRenderer.render(
+
+ );
+
+ const agentActionsButton = result.getByTestId('agentActionsBtn');
+ agentActionsButton.click();
+
+ const deleteButton = result.getByTestId('agentPolicyActionMenuDeleteButton');
+ expect(deleteButton).not.toHaveAttribute('disabled');
+ });
+
+ it('is disabled when a managed package policy is present', () => {
+ const testRenderer = createFleetTestRendererMock();
+ const agentPolicyWithManagedPackagePolicy: AgentPolicy = {
+ ...baseAgentPolicy,
+ package_policies: [
+ {
+ id: 'test-package-policy',
+ is_managed: true,
+ created_at: new Date().toISOString(),
+ created_by: 'test',
+ enabled: true,
+ inputs: [],
+ name: 'test-package-policy',
+ namespace: 'default',
+ policy_id: 'test',
+ revision: 1,
+ updated_at: new Date().toISOString(),
+ updated_by: 'test',
+ },
+ ],
+ };
+
+ const result = testRenderer.render(
+
+ );
+
+ const agentActionsButton = result.getByTestId('agentActionsBtn');
+ agentActionsButton.click();
+
+ const deleteButton = result.getByTestId('agentPolicyActionMenuDeleteButton');
+ expect(deleteButton).toHaveAttribute('disabled');
+ });
+ });
+});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx
index 16efc77f25fe1..fa97cf08f6d91 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx
@@ -18,10 +18,11 @@ import {
} from '../../../components';
import { FLEET_SERVER_PACKAGE } from '../../../constants';
-import { ExperimentalFeaturesService } from '../../../../../services/experimental_features';
+import { policyHasFleetServer, ExperimentalFeaturesService } from '../../../services';
import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout';
import { AgentPolicyCopyProvider } from './agent_policy_copy_provider';
+import { AgentPolicyDeleteProvider } from './agent_policy_delete_provider';
export const AgentPolicyActionMenu = memo<{
agentPolicy: AgentPolicy;
@@ -55,6 +56,10 @@ export const AgentPolicyActionMenu = memo<{
[agentPolicy]
);
+ const hasManagedPackagePolicy =
+ 'package_policies' in agentPolicy &&
+ agentPolicy?.package_policies?.some((packagePolicy) => packagePolicy.is_managed);
+
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
const onContextMenuChange = useCallback(
@@ -129,6 +134,35 @@ export const AgentPolicyActionMenu = memo<{
defaultMessage="Duplicate policy"
/>
,
+
+ {(deleteAgentPolicyPrompt) => (
+
+ ) : undefined
+ }
+ icon="trash"
+ onClick={() => {
+ deleteAgentPolicyPrompt(agentPolicy.id);
+ }}
+ >
+
+
+ )}
+ ,
];
if (agentTamperProtectionEnabled && !agentPolicy?.is_managed) {
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx
index 49288da22c935..686934377fdf3 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx
@@ -13,7 +13,6 @@ import {
EuiComboBox,
EuiIconTip,
EuiCheckboxGroup,
- EuiButton,
EuiLink,
EuiFieldNumber,
EuiFieldText,
@@ -41,10 +40,9 @@ import { useStartServices, useConfig, useGetAgentPolicies, useLicense } from '..
import { AgentPolicyPackageBadge } from '../../../../components';
import { UninstallCommandFlyout } from '../../../../../../components';
-import { AgentPolicyDeleteProvider } from '../agent_policy_delete_provider';
import type { ValidationResults } from '../agent_policy_validation';
-import { ExperimentalFeaturesService, policyHasFleetServer } from '../../../../services';
+import { ExperimentalFeaturesService } from '../../../../services';
import { policyHasEndpointSecurity as hasElasticDefend } from '../../../../../../../common/services';
@@ -60,7 +58,6 @@ interface Props {
updateAgentPolicy: (u: Partial) => void;
validation: ValidationResults;
isEditing?: boolean;
- onDelete?: () => void;
}
export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = ({
@@ -68,7 +65,6 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent =
updateAgentPolicy,
validation,
isEditing = false,
- onDelete = () => {},
}) => {
const { docLinks } = useStartServices();
const config = useConfig();
@@ -101,10 +97,6 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent =
// agent monitoring checkbox group can appear multiple times in the DOM, ids have to be unique to work correctly
const monitoringCheckboxIdSuffix = Date.now();
- const hasManagedPackagePolicy =
- 'package_policies' in agentPolicy &&
- agentPolicy?.package_policies?.some((packagePolicy) => packagePolicy.is_managed);
-
const { agentTamperProtectionEnabled } = ExperimentalFeaturesService.get();
const licenseService = useLicense();
const [isUninstallCommandFlyoutOpen, setIsUninstallCommandFlyoutOpen] = useState(false);
@@ -725,57 +717,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent =
/>
- {isEditing && 'id' in agentPolicy && !agentPolicy.is_managed ? (
-
-
-
- }
- description={
- <>
-
-
-
- {(deleteAgentPolicyPrompt) => {
- return (
-
- ) : undefined
- }
- >
- deleteAgentPolicyPrompt(agentPolicy.id!, onDelete)}
- isDisabled={hasManagedPackagePolicy}
- >
-
-
-
- );
- }}
-
- >
- }
- />
- ) : null}
+
>
);
};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx
index 7abeb00dfb65d..b3f06abf72e12 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_create_inline.tsx
@@ -189,7 +189,6 @@ export const AgentPolicyCreateInlineForm: React.FunctionComponent = ({
updateAgentPolicy={updateNewAgentPolicy}
validation={validation}
isEditing={false}
- onDelete={() => {}}
/>
>
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
index e4720bad5a091..e3bf5fb72dfe3 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
@@ -36,7 +36,6 @@ interface Props {
updateSysMonitoring: (newValue: boolean) => void;
validation: ValidationResults;
isEditing?: boolean;
- onDelete?: () => void;
}
export const AgentPolicyForm: React.FunctionComponent = ({
@@ -46,7 +45,6 @@ export const AgentPolicyForm: React.FunctionComponent = ({
updateSysMonitoring,
validation,
isEditing = false,
- onDelete = () => {},
}) => {
const generalSettingsWrapper = (children: JSX.Element[]) => (
= ({
updateAgentPolicy={updateAgentPolicy}
validation={validation}
isEditing={isEditing}
- onDelete={onDelete}
/>
>
@@ -122,7 +119,6 @@ export const AgentPolicyForm: React.FunctionComponent = ({
updateAgentPolicy={updateAgentPolicy}
validation={validation}
isEditing={isEditing}
- onDelete={onDelete}
/>
)}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_integration.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_integration.tsx
index da7ca2f3e18ec..759c85b54a181 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_integration.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_integration.tsx
@@ -102,7 +102,6 @@ export const AgentPolicyIntegrationForm: React.FunctionComponent = ({
updateAgentPolicy={updateAgentPolicy}
validation={validation}
isEditing={isEditing}
- onDelete={onDelete}
/>
>
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
index 0728a3d369d87..eb77e63fd2c82 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
@@ -6,7 +6,6 @@
*/
import React, { memo, useMemo, useState } from 'react';
-import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { pick } from 'lodash';
import {
@@ -22,7 +21,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { AgentPolicy } from '../../../../../types';
import {
- useLink,
useStartServices,
useAuthz,
sendUpdateAgentPolicy,
@@ -69,8 +67,6 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
const {
agents: { enabled: isFleetEnabled },
} = useConfig();
- const history = useHistory();
- const { getPath } = useLink();
const hasFleetAllPrivileges = useAuthz().fleet.all;
const refreshAgentPolicy = useAgentPolicyRefresh();
const [agentPolicy, setAgentPolicy] = useState({
@@ -173,9 +169,6 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
updateSysMonitoring={(newValue) => setWithSysMonitoring(newValue)}
validation={validation}
isEditing={true}
- onDelete={() => {
- history.push(getPath('policies_list'));
- }}
/>
{hasChanges ? (
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
index 0e485de1e7775..97d092fc36b26 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
@@ -16,10 +16,14 @@ import {
uiSettingsServiceMock,
themeServiceMock,
executionContextServiceMock,
+ applicationServiceMock,
+ fatalErrorsServiceMock,
+ httpServiceMock,
} from '@kbn/core/public/mocks';
import { GlobalFlyout } from '@kbn/es-ui-shared-plugin/public';
+import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
-
+import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { settingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { MAJOR_VERSION } from '../../../common';
import { AppContextProvider } from '../../../public/application/app_context';
@@ -52,15 +56,23 @@ setUiMetricService(services.uiMetricService);
const appDependencies = {
services,
core: {
- getUrlForApp: () => {},
+ getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp,
executionContext: executionContextServiceMock.createStartContract(),
+ http: httpServiceMock.createSetupContract(),
+ application: applicationServiceMock.createStartContract(),
+ fatalErrors: fatalErrorsServiceMock.createSetupContract(),
+ },
+ plugins: {
+ usageCollection: usageCollectionPluginMock.createSetupContract(),
+ isFleetEnabled: false,
+ share: sharePluginMock.createStartContract(),
},
- plugins: {},
// Default stateful configuration
config: {
enableLegacyTemplates: true,
enableIndexActions: true,
enableIndexStats: true,
+ enableIndexDetailsPage: false,
},
} as any;
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 a92df925be1a6..24cf015e3918d 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
@@ -76,6 +76,11 @@ export interface IndexDetailsPageTestBed extends TestBed {
indexStatsTabExists: () => boolean;
isWarningDisplayed: () => boolean;
};
+ overview: {
+ indexStatsContentExists: () => boolean;
+ indexDetailsContentExists: () => boolean;
+ addDocCodeBlockExists: () => boolean;
+ };
};
}
@@ -116,6 +121,18 @@ export const setup = async (
return find('indexDetailsContent').text();
};
+ const overview = {
+ indexStatsContentExists: () => {
+ return exists('overviewTabIndexStats');
+ },
+ indexDetailsContentExists: () => {
+ return exists('overviewTabIndexDetails');
+ },
+ addDocCodeBlockExists: () => {
+ return exists('codeBlockControlsPanel');
+ },
+ };
+
const mappings = {
getCodeBlockContent: () => {
return find('indexDetailsMappingsCodeBlock').text();
@@ -258,6 +275,7 @@ export const setup = async (
getActiveTabContent,
mappings,
settings,
+ overview,
clickBackToIndicesButton,
discoverLinkExists,
contextMenu,
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 62fe47b2529d4..78a9369222477 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
@@ -186,9 +186,24 @@ describe('', () => {
expect(header).toEqual(testIndexName);
});
- it('defaults to overview tab', () => {
- const tabContent = testBed.actions.getActiveTabContent();
- expect(tabContent).toEqual('Overview');
+ describe('Overview tab', () => {
+ it('renders index details', () => {
+ expect(testBed.actions.overview.indexDetailsContentExists()).toBe(true);
+ expect(testBed.actions.overview.indexStatsContentExists()).toBe(true);
+ expect(testBed.actions.overview.addDocCodeBlockExists()).toBe(true);
+ });
+
+ it('hides index stats from detail panels if enableIndexStats===false', async () => {
+ await act(async () => {
+ testBed = await setup(httpSetup, {
+ config: { enableIndexStats: false },
+ });
+ });
+ testBed.component.update();
+
+ expect(testBed.actions.overview.indexDetailsContentExists()).toBe(true);
+ expect(testBed.actions.overview.indexStatsContentExists()).toBe(false);
+ });
});
it('documents tab', async () => {
diff --git a/x-pack/plugins/index_management/kibana.jsonc b/x-pack/plugins/index_management/kibana.jsonc
index 47171db66450d..250fde111d29b 100644
--- a/x-pack/plugins/index_management/kibana.jsonc
+++ b/x-pack/plugins/index_management/kibana.jsonc
@@ -19,7 +19,8 @@
"optionalPlugins": [
"security",
"usageCollection",
- "fleet"
+ "fleet",
+ "cloud"
],
"requiredBundles": [
"kibanaReact",
diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx
index fcedba9a1b941..f23baeb342c00 100644
--- a/x-pack/plugins/index_management/public/application/app_context.tsx
+++ b/x-pack/plugins/index_management/public/application/app_context.tsx
@@ -18,10 +18,12 @@ import {
DocLinksStart,
IUiSettingsClient,
ExecutionContextStart,
+ HttpSetup,
} from '@kbn/core/public';
-import { SharePluginStart } from '@kbn/share-plugin/public';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
+import type { CloudSetup } from '@kbn/cloud-plugin/public';
import { ExtensionsService } from '../services';
import { UiMetricService, NotificationService, HttpService } from './services';
@@ -33,10 +35,13 @@ export interface AppDependencies {
getUrlForApp: ApplicationStart['getUrlForApp'];
executionContext: ExecutionContextStart;
application: ApplicationStart;
+ http: HttpSetup;
};
plugins: {
usageCollection: UsageCollectionSetup;
isFleetEnabled: boolean;
+ share: SharePluginStart;
+ cloud?: CloudSetup;
};
services: {
uiMetricService: UiMetricService;
diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts
index df723e21ae287..10caf3bcc5a57 100644
--- a/x-pack/plugins/index_management/public/application/mount_management_section.ts
+++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts
@@ -11,6 +11,7 @@ import { CoreSetup, CoreStart } from '@kbn/core/public';
import { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
+import { CloudSetup } from '@kbn/cloud-plugin/public';
import { UIM_APP_NAME } from '../../common/constants';
import { PLUGIN } from '../../common/constants/plugin';
import { ExtensionsService } from '../services';
@@ -57,6 +58,7 @@ export async function mountManagementSection({
enableLegacyTemplates = true,
enableIndexDetailsPage = false,
enableIndexStats = true,
+ cloud,
}: {
coreSetup: CoreSetup;
usageCollection: UsageCollectionSetup;
@@ -68,6 +70,7 @@ export async function mountManagementSection({
enableLegacyTemplates?: boolean;
enableIndexDetailsPage?: boolean;
enableIndexStats?: boolean;
+ cloud?: CloudSetup;
}) {
const { element, setBreadcrumbs, history, theme$ } = params;
const [core, startDependencies] = await coreSetup.getStartServices();
@@ -79,6 +82,7 @@ export async function mountManagementSection({
uiSettings,
executionContext,
settings,
+ http,
} = core;
const { url } = startDependencies.share;
@@ -98,10 +102,13 @@ export async function mountManagementSection({
getUrlForApp: application.getUrlForApp,
executionContext,
application,
+ http,
},
plugins: {
usageCollection,
isFleetEnabled,
+ share: startDependencies.share,
+ cloud,
},
services: {
httpService,
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx
index 17cbeb7649449..33378fc138c04 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx
@@ -30,6 +30,7 @@ import { DetailsPageError } from './details_page_error';
import { ManageIndexButton } from './manage_index_button';
import { DetailsPageStats } from './details_page_stats';
import { DetailsPageMappings } from './details_page_mappings';
+import { DetailsPageOverview } from './details_page_overview';
import { DetailsPageSettings } from './details_page_settings';
export enum IndexDetailsSection {
@@ -189,7 +190,7 @@ export const DetailsPage: React.FunctionComponent<
Overview
}
+ render={() => }
/>
= ({ indexDetails }) => {
+ const {
+ name,
+ status,
+ documents,
+ documents_deleted: documentsDeleted,
+ primary,
+ replica,
+ aliases,
+ } = indexDetails;
+ const { config, core, plugins } = useAppContext();
+
+ const [selectedLanguage, setSelectedLanguage] = useState(curlDefinition);
+
+ const elasticsearchURL = useMemo(() => {
+ return plugins.cloud?.elasticsearchUrl ?? 'https://your_deployment_url';
+ }, [plugins.cloud]);
+
+ const codeSnippetArguments: LanguageDefinitionSnippetArguments = {
+ url: elasticsearchURL,
+ apiKey: 'your_api_key',
+ indexName: name,
+ };
+
+ return (
+ <>
+
+ {config.enableIndexStats && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {primary && (
+
+
+
+ )}
+
+ {replica && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.addMoreDataTitle', {
+ defaultMessage: 'Add more data to this index',
+ })}
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.addMoreDataDescription', {
+ defaultMessage:
+ 'Keep adding more documents to your already created index using the API',
+ })}
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/index.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/index.ts
new file mode 100644
index 0000000000000..e3f5b65bdcadf
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/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 { DetailsPageOverview } from './details_page_overview';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts
new file mode 100644
index 0000000000000..838efe28580c0
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts
@@ -0,0 +1,179 @@
+/*
+ * 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 { Languages, LanguageDefinition } from '@kbn/search-api-panels';
+import { i18n } from '@kbn/i18n';
+
+const INDEX_NAME_PLACEHOLDER = 'index_name';
+
+export const curlDefinition: LanguageDefinition = {
+ id: Languages.CURL,
+ name: i18n.translate('xpack.idxMgmt.indexDetails.languages.cURL', {
+ defaultMessage: 'cURL',
+ }),
+ iconType: 'curl.svg',
+ languageStyling: 'shell',
+ ingestDataIndex: ({ apiKey, indexName, url }) => `curl -X POST ${url}/_bulk?pretty \\
+ -H "Authorization: ApiKey ${apiKey}" \\
+ -H "Content-Type: application/json" \\
+ -d'
+{ "index" : { "_index" : "${indexName ?? INDEX_NAME_PLACEHOLDER}" } }
+{"name": "foo", "title": "bar" }
+`,
+};
+
+export const javascriptDefinition: LanguageDefinition = {
+ id: Languages.JAVASCRIPT,
+ name: i18n.translate('xpack.idxMgmt.indexDetails.languages.javascript', {
+ defaultMessage: 'JavaScript',
+ }),
+ iconType: 'javascript.svg',
+ ingestDataIndex: ({
+ apiKey,
+ url,
+ indexName,
+ }) => `const { Client } = require('@elastic/elasticsearch');
+const client = new Client({
+ node: '${url}',
+ auth: {
+ apiKey: '${apiKey}'
+ }
+});
+const dataset = [
+ {'name': 'foo', 'title': 'bar'},
+];
+
+// Index with the bulk helper
+const result = await client.helpers.bulk({
+ datasource: dataset,
+ onDocument (doc) {
+ return { index: { _index: '${indexName ?? 'index_name'}' }};
+ }
+});
+console.log(result);
+`,
+};
+
+export const goDefinition: LanguageDefinition = {
+ id: Languages.GO,
+ name: i18n.translate('xpack.idxMgmt.indexDetails.languages.go', {
+ defaultMessage: 'Go',
+ }),
+ iconType: 'go.svg',
+ ingestDataIndex: ({ apiKey, url, indexName }) => `import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "github.com/elastic/elasticsearch-serverless-go"
+)
+
+func main() {
+ cfg := elasticsearch.Config{
+ Address: "${url}",
+ APIKey: "${apiKey}",
+ }
+ es, err := elasticsearch.NewClient(cfg)
+ if err != nil {
+ log.Fatalf("Error creating the client: %s", err)
+ }
+ res, err := es.Bulk().
+ Index("${indexName}").
+ Raw(strings.NewReader(\`
+{ "index": { "_id": "1"}}
+{"name": "foo", "title": "bar"}\n\`)).
+ Do(context.Background())
+
+ fmt.Println(res, err)
+}`,
+};
+
+export const pythonDefinition: LanguageDefinition = {
+ id: Languages.PYTHON,
+ name: i18n.translate('xpack.idxMgmt.indexDetails.languages.python', {
+ defaultMessage: 'Python',
+ }),
+ iconType: 'python.svg',
+ ingestDataIndex: ({ apiKey, url, indexName }) => `from elasticsearch import Elasticsearch
+
+client = Elasticsearch(
+ "${url}",
+ api_key="${apiKey}"
+)
+
+documents = [
+ {"index": {"_index": "${indexName ?? INDEX_NAME_PLACEHOLDER}"}},
+ {"name": "foo", "title": "bar"},
+]
+
+client.bulk(operations=documents)
+`,
+};
+
+export const phpDefinition: LanguageDefinition = {
+ id: Languages.PHP,
+ name: i18n.translate('xpack.idxMgmt.indexDetails.languages.php', {
+ defaultMessage: 'PHP',
+ }),
+ iconType: 'php.svg',
+ ingestDataIndex: ({ apiKey, url, indexName }) => `$client = ClientBuilder::create()
+ ->setHosts(['${url}'])
+ ->setApiKey('${apiKey}')
+ ->build();
+
+$params = [
+'body' => [
+[
+'index' => [
+'_index' => '${indexName ?? INDEX_NAME_PLACEHOLDER}',
+'_id' => '1',
+],
+],
+[
+'name' => 'foo',
+'title' => 'bar',
+],
+],
+];
+
+$response = $client->bulk($params);
+echo $response->getStatusCode();
+echo (string) $response->getBody();
+`,
+};
+
+export const rubyDefinition: LanguageDefinition = {
+ id: Languages.RUBY,
+ name: i18n.translate('xpack.idxMgmt.indexDetails.languages.ruby', {
+ defaultMessage: 'Ruby',
+ }),
+ iconType: 'ruby.svg',
+ ingestDataIndex: ({ apiKey, url, indexName }) => `client = ElasticsearchServerless::Client.new(
+ api_key: '${apiKey}',
+ url: '${url}'
+)
+
+documents = [
+ { index: { _index: '${
+ indexName ?? INDEX_NAME_PLACEHOLDER
+ }', data: {name: "foo", "title": "bar"} } },
+]
+client.bulk(body: documents)
+`,
+};
+
+const languageDefinitionRecords: Partial> = {
+ [Languages.CURL]: curlDefinition,
+ [Languages.PYTHON]: pythonDefinition,
+ [Languages.JAVASCRIPT]: javascriptDefinition,
+ [Languages.PHP]: phpDefinition,
+ [Languages.GO]: goDefinition,
+ [Languages.RUBY]: rubyDefinition,
+};
+
+export const languageDefinitions: LanguageDefinition[] = Object.values(languageDefinitionRecords);
diff --git a/x-pack/plugins/index_management/public/assets/curl.svg b/x-pack/plugins/index_management/public/assets/curl.svg
new file mode 100644
index 0000000000000..e922b12283f7d
--- /dev/null
+++ b/x-pack/plugins/index_management/public/assets/curl.svg
@@ -0,0 +1,6 @@
+
diff --git a/x-pack/plugins/index_management/public/assets/go.svg b/x-pack/plugins/index_management/public/assets/go.svg
new file mode 100644
index 0000000000000..715978f645771
--- /dev/null
+++ b/x-pack/plugins/index_management/public/assets/go.svg
@@ -0,0 +1,11 @@
+
diff --git a/x-pack/plugins/index_management/public/assets/javascript.svg b/x-pack/plugins/index_management/public/assets/javascript.svg
new file mode 100644
index 0000000000000..6d514f5448c50
--- /dev/null
+++ b/x-pack/plugins/index_management/public/assets/javascript.svg
@@ -0,0 +1,11 @@
+
diff --git a/x-pack/plugins/index_management/public/assets/php.svg b/x-pack/plugins/index_management/public/assets/php.svg
new file mode 100644
index 0000000000000..df4ae0ccc1863
--- /dev/null
+++ b/x-pack/plugins/index_management/public/assets/php.svg
@@ -0,0 +1,3 @@
+
diff --git a/x-pack/plugins/index_management/public/assets/python.svg b/x-pack/plugins/index_management/public/assets/python.svg
new file mode 100644
index 0000000000000..bd8a27810c575
--- /dev/null
+++ b/x-pack/plugins/index_management/public/assets/python.svg
@@ -0,0 +1,19 @@
+
diff --git a/x-pack/plugins/index_management/public/assets/ruby.svg b/x-pack/plugins/index_management/public/assets/ruby.svg
new file mode 100644
index 0000000000000..55d2d7eea4f45
--- /dev/null
+++ b/x-pack/plugins/index_management/public/assets/ruby.svg
@@ -0,0 +1,3 @@
+
diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts
index 401c37770fd79..33006b7b21cde 100644
--- a/x-pack/plugins/index_management/public/plugin.ts
+++ b/x-pack/plugins/index_management/public/plugin.ts
@@ -45,7 +45,7 @@ export class IndexMgmtUIPlugin {
} = this.ctx.config.get();
if (isIndexManagementUiEnabled) {
- const { fleet, usageCollection, management } = plugins;
+ const { fleet, usageCollection, management, cloud } = plugins;
const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version);
management.sections.section.data.registerApp({
id: PLUGIN.id,
@@ -64,6 +64,7 @@ export class IndexMgmtUIPlugin {
enableLegacyTemplates,
enableIndexDetailsPage,
enableIndexStats,
+ cloud,
});
},
});
diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts
index 7ca83e70b5e0f..78519e9452402 100644
--- a/x-pack/plugins/index_management/public/types.ts
+++ b/x-pack/plugins/index_management/public/types.ts
@@ -7,7 +7,8 @@
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { ManagementSetup } from '@kbn/management-plugin/public';
-import { SharePluginStart } from '@kbn/share-plugin/public';
+import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
+import { CloudSetup } from '@kbn/cloud-plugin/public';
import { ExtensionsSetup, PublicApiServiceSetup } from './services';
export interface IndexManagementPluginSetup {
@@ -19,6 +20,8 @@ export interface SetupDependencies {
fleet?: unknown;
usageCollection: UsageCollectionSetup;
management: ManagementSetup;
+ share: SharePluginSetup;
+ cloud?: CloudSetup;
}
export interface StartDependencies {
diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json
index 2df96ae23c47a..90514823c5ac0 100644
--- a/x-pack/plugins/index_management/tsconfig.json
+++ b/x-pack/plugins/index_management/tsconfig.json
@@ -36,6 +36,8 @@
"@kbn/core-ui-settings-browser",
"@kbn/kibana-utils-plugin",
"@kbn/core-http-browser",
+ "@kbn/search-api-panels",
+ "@kbn/cloud-plugin",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/infra/public/common/visualizations/constants.ts b/x-pack/plugins/infra/public/common/visualizations/constants.ts
index d24f937770d31..1ad738cb02706 100644
--- a/x-pack/plugins/infra/public/common/visualizations/constants.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/constants.ts
@@ -20,7 +20,7 @@ import {
diskWriteThroughput,
diskSpaceAvailability,
diskSpaceAvailable,
- diskSpaceUsage,
+ diskUsage,
logRate,
normalizedLoad1m,
load1m,
@@ -58,7 +58,7 @@ export const hostLensFormulas = {
diskWriteThroughput,
diskSpaceAvailability,
diskSpaceAvailable,
- diskSpaceUsage,
+ diskUsage,
hostCount,
logRate,
normalizedLoad1m,
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_kpi_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_kpi_charts.ts
index a781a0404eb0c..b911eadbc6583 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_kpi_charts.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_kpi_charts.ts
@@ -97,15 +97,15 @@ export const hostKPICharts: KPIChartProps[] = [
},
{
id: 'diskSpaceUsage',
- title: i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title', {
- defaultMessage: 'Disk Space Usage',
+ title: i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.kpi.diskUsage.title', {
+ defaultMessage: 'Disk Usage',
}),
layers: {
data: {
- ...hostLensFormulas.diskSpaceUsage,
- format: hostLensFormulas.diskSpaceUsage.format
+ ...hostLensFormulas.diskUsage,
+ format: hostLensFormulas.diskUsage.format
? {
- ...hostLensFormulas.diskSpaceUsage.format,
+ ...hostLensFormulas.diskUsage.format,
params: {
decimals: 1,
},
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts
index 5da21aba45c33..8a116bdb40ce8 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts
@@ -9,7 +9,7 @@ import {
diskSpaceUsageAvailable,
diskThroughputReadWrite,
diskIOReadWrite,
- diskSpaceUsageByMountPoint,
+ diskUsageByMountPoint,
} from '../metric_charts/disk';
import { logRate } from '../metric_charts/log';
import { memoryUsage, memoryUsageBreakdown } from '../metric_charts/memory';
@@ -22,7 +22,7 @@ export const hostMetricFlyoutCharts: XYConfig[] = [
normalizedLoad1m,
logRate,
diskSpaceUsageAvailable,
- diskSpaceUsageByMountPoint,
+ diskUsageByMountPoint,
diskThroughputReadWrite,
diskIOReadWrite,
rxTx,
@@ -37,7 +37,7 @@ export const hostMetricChartsFullPage: XYConfig[] = [
loadBreakdown,
logRate,
diskSpaceUsageAvailable,
- diskSpaceUsageByMountPoint,
+ diskUsageByMountPoint,
diskThroughputReadWrite,
diskIOReadWrite,
rxTx,
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts
index 8d2daa435fbdc..b6c20c7abaa20 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/metric_charts/disk.ts
@@ -14,22 +14,22 @@ const TOP_VALUES_SIZE = 5;
export const diskSpaceUsageAvailable: XYConfig = {
id: 'diskSpaceUsageAvailable',
- title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpace', {
- defaultMessage: 'Disk Space',
+ title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsage', {
+ defaultMessage: 'Disk Usage',
}),
layers: [
{
data: [
{
- ...hostLensFormulas.diskSpaceUsage,
- label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpace.label.used', {
+ ...hostLensFormulas.diskUsage,
+ label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsage.label.used', {
defaultMessage: 'Used',
}),
},
{
...hostLensFormulas.diskSpaceAvailability,
label: i18n.translate(
- 'xpack.infra.assetDetails.metricsCharts.diskSpace.label.available',
+ 'xpack.infra.assetDetails.metricsCharts.diskUsage.label.available',
{
defaultMessage: 'Available',
}
@@ -49,17 +49,17 @@ export const diskSpaceUsageAvailable: XYConfig = {
dataViewOrigin: 'metrics',
};
-export const diskSpaceUsageByMountPoint: XYConfig = {
- id: 'DiskSpaceUsageByMountPoint',
- title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpaceByMountingPoint', {
- defaultMessage: 'Disk Space by Mount Point',
+export const diskUsageByMountPoint: XYConfig = {
+ id: 'DiskUsageByMountPoint',
+ title: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsageByMountingPoint', {
+ defaultMessage: 'Disk Usage by Mount Point',
}),
layers: [
{
data: [
{
- ...hostLensFormulas.diskSpaceUsage,
- label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskSpace.label.used', {
+ ...hostLensFormulas.diskUsage,
+ label: i18n.translate('xpack.infra.assetDetails.metricsCharts.diskUsage.label.used', {
defaultMessage: 'Used',
}),
},
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts
index 78ea831ff2c5d..eee6ffca3ab2a 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/hosts_view/hosts_metric_charts.ts
@@ -83,12 +83,12 @@ export const hostsMetricCharts: Array<
},
{
id: 'diskSpaceUsed',
- title: i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed', {
- defaultMessage: 'Disk Space Usage',
+ title: i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.diskUsage', {
+ defaultMessage: 'Disk Usage',
}),
layers: [
{
- data: [hostLensFormulas.diskSpaceUsage],
+ data: [hostLensFormulas.diskUsage],
options: XY_LAYER_OPTIONS,
type: 'visualization',
},
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_usage.ts
similarity index 84%
rename from x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts
rename to x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_usage.ts
index 24143b58c81e6..c45ebac82a9e8 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_space_usage.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/disk_usage.ts
@@ -7,8 +7,8 @@
import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-export const diskSpaceUsage: FormulaValueConfig = {
- label: 'Disk Space Usage',
+export const diskUsage: FormulaValueConfig = {
+ label: 'Disk Usage',
value: 'average(system.filesystem.used.pct)',
format: {
id: 'percent',
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts
index 0635992ba1792..0b39cda885b4c 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts
@@ -19,7 +19,7 @@ export { diskReadThroughput } from './disk_read_throughput';
export { diskWriteThroughput } from './disk_write_throughput';
export { diskSpaceAvailability } from './disk_space_availability';
export { diskSpaceAvailable } from './disk_space_available';
-export { diskSpaceUsage } from './disk_space_usage';
+export { diskUsage } from './disk_usage';
export { hostCount } from './host_count';
export { logRate } from './log_rate';
export { normalizedLoad1m } from './normalized_load_1m';
diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts
index 05e17c16eb929..b6e35dbf51773 100644
--- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts
@@ -6,42 +6,42 @@
*/
import { i18n } from '@kbn/i18n';
-import { type AssetDetailsProps, FlyoutTabIds, type Tab } from '../../../types';
+import { type AssetDetailsProps, ContentTabIds, type Tab } from '../../../types';
const links: AssetDetailsProps['links'] = ['alertRule', 'nodeDetails', 'apmServices'];
const tabs: Tab[] = [
{
- id: FlyoutTabIds.OVERVIEW,
+ id: ContentTabIds.OVERVIEW,
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
defaultMessage: 'Overview',
}),
},
{
- id: FlyoutTabIds.LOGS,
+ id: ContentTabIds.LOGS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', {
defaultMessage: 'Logs',
}),
},
{
- id: FlyoutTabIds.METADATA,
+ id: ContentTabIds.METADATA,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.metadata', {
defaultMessage: 'Metadata',
}),
},
{
- id: FlyoutTabIds.PROCESSES,
+ id: ContentTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
},
{
- id: FlyoutTabIds.ANOMALIES,
+ id: ContentTabIds.ANOMALIES,
name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', {
defaultMessage: 'Anomalies',
}),
},
{
- id: FlyoutTabIds.LINK_TO_APM,
+ id: ContentTabIds.LINK_TO_APM,
name: i18n.translate('xpack.infra.infra.nodeDetails.apmTabLabel', {
defaultMessage: 'APM',
}),
diff --git a/x-pack/plugins/infra/public/components/asset_details/constants.ts b/x-pack/plugins/infra/public/components/asset_details/constants.ts
index 726f47450d0cc..cdd5b95082158 100644
--- a/x-pack/plugins/infra/public/components/asset_details/constants.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/constants.ts
@@ -8,3 +8,5 @@
export const ASSET_DETAILS_FLYOUT_COMPONENT_NAME = 'infraAssetDetailsFlyout';
export const METRIC_CHART_HEIGHT = 300;
export const APM_HOST_FILTER_FIELD = 'host.hostname';
+
+export const ASSET_DETAILS_URL_STATE_KEY = 'assetDetails';
diff --git a/x-pack/plugins/infra/public/components/asset_details/content/content.tsx b/x-pack/plugins/infra/public/components/asset_details/content/content.tsx
index c9d622cf01493..0cf51f1fff3d7 100644
--- a/x-pack/plugins/infra/public/components/asset_details/content/content.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/content/content.tsx
@@ -10,7 +10,7 @@ import React from 'react';
import { DatePicker } from '../date_picker/date_picker';
import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
import { Anomalies, Metadata, Processes, Osquery, Logs, Overview } from '../tabs';
-import { FlyoutTabIds } from '../types';
+import { ContentTabIds } from '../types';
export const Content = () => {
return (
@@ -18,31 +18,31 @@ export const Content = () => {
-
+
-
+
-
+
-
+
-
+
-
+
@@ -50,11 +50,11 @@ export const Content = () => {
);
};
-const DatePickerWrapper = ({ visibleFor }: { visibleFor: FlyoutTabIds[] }) => {
+const DatePickerWrapper = ({ visibleFor }: { visibleFor: ContentTabIds[] }) => {
const { activeTabId } = useTabSwitcherContext();
return (
-
+
);
@@ -64,7 +64,7 @@ const TabPanel = ({
activeWhen,
children,
}: {
- activeWhen: FlyoutTabIds;
+ activeWhen: ContentTabIds;
children: React.ReactNode;
}) => {
const { renderedTabsSet, activeTabId } = useTabSwitcherContext();
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_render_props.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_render_props.ts
index 6ddba44e6da7e..eab5f8326f525 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_render_props.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_render_props.ts
@@ -7,6 +7,7 @@
import createContainer from 'constate';
import type { AssetDetailsProps } from '../types';
+import { useAssetDetailsUrlState } from './use_asset_details_url_state';
import { useMetadataStateProviderContext } from './use_metadata_state';
export interface UseAssetDetailsRenderProps {
@@ -14,17 +15,18 @@ export interface UseAssetDetailsRenderProps {
}
export function useAssetDetailsRenderProps({ props }: UseAssetDetailsRenderProps) {
+ const [urlState] = useAssetDetailsUrlState();
const { metadata } = useMetadataStateProviderContext();
const { asset, assetType, overrides, renderMode } = props;
// When the asset asset.name is known we can load the page faster
// Otherwise we need to use metadata response.
- const loading = !asset.name && !metadata?.name;
+ const loading = !urlState?.name && !asset.name && !metadata?.name;
return {
asset: {
...asset,
- name: asset.name || metadata?.name || 'asset-name',
+ name: urlState?.name || asset.name || metadata?.name || '',
},
assetType,
overrides,
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts
index 370778555f7d0..525a5b635e19b 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts
@@ -9,15 +9,15 @@ import * as rt from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
-import { FlyoutTabIds } from '../types';
+import { ContentTabIds } from '../types';
import { useUrlState } from '../../../utils/use_url_state';
+import { ASSET_DETAILS_URL_STATE_KEY } from '../constants';
+import { getDefaultDateRange } from '../utils';
-export const DEFAULT_STATE: AssetDetailsState = {
- tabId: FlyoutTabIds.OVERVIEW,
- processSearch: undefined,
- metadataSearch: undefined,
+export const DEFAULT_STATE: AssetDetailsUrlState = {
+ tabId: ContentTabIds.OVERVIEW,
+ dateRange: getDefaultDateRange(),
};
-const ASSET_DETAILS_URL_STATE_KEY = 'asset_details';
type SetAssetDetailsState = (newProp: Payload | null) => void;
@@ -44,34 +44,35 @@ export const useAssetDetailsUrlState = (): [AssetDetailsUrl, SetAssetDetailsStat
};
const TabIdRT = rt.union([
- rt.literal(FlyoutTabIds.OVERVIEW),
- rt.literal(FlyoutTabIds.METADATA),
- rt.literal(FlyoutTabIds.PROCESSES),
- rt.literal(FlyoutTabIds.LOGS),
- rt.literal(FlyoutTabIds.ANOMALIES),
- rt.literal(FlyoutTabIds.OSQUERY),
+ rt.literal(ContentTabIds.OVERVIEW),
+ rt.literal(ContentTabIds.METADATA),
+ rt.literal(ContentTabIds.PROCESSES),
+ rt.literal(ContentTabIds.LOGS),
+ rt.literal(ContentTabIds.ANOMALIES),
+ rt.literal(ContentTabIds.OSQUERY),
]);
-const AssetDetailsStateRT = rt.intersection([
+const AssetDetailsUrlStateRT = rt.intersection([
rt.type({
- tabId: TabIdRT,
- }),
- rt.partial({
dateRange: rt.type({
from: rt.string,
to: rt.string,
}),
+ }),
+ rt.partial({
+ tabId: TabIdRT,
+ name: rt.string,
processSearch: rt.string,
metadataSearch: rt.string,
logsSearch: rt.string,
}),
]);
-const AssetDetailsUrlRT = rt.union([AssetDetailsStateRT, rt.null]);
+const AssetDetailsUrlRT = rt.union([AssetDetailsUrlStateRT, rt.null]);
-export type AssetDetailsState = rt.TypeOf
;
+export type AssetDetailsUrlState = rt.TypeOf;
type AssetDetailsUrl = rt.TypeOf;
-type Payload = Partial;
+type Payload = Partial;
const encodeUrlState = AssetDetailsUrlRT.encode;
const decodeUrlState = (value: unknown) => {
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts
index 24f5e56ecedb6..345d197f23945 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts
@@ -10,24 +10,16 @@ import createContainer from 'constate';
import { useCallback, useState } from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { parseDateRange } from '../../../utils/datemath';
-import { toTimestampRange } from '../utils';
+import { getDefaultDateRange, toTimestampRange } from '../utils';
import { useAssetDetailsUrlState } from './use_asset_details_url_state';
export interface UseDateRangeProviderProps {
initialDateRange: TimeRange;
}
-const DEFAULT_FROM_IN_MILLISECONDS = 15 * 60000;
-const getDefaultDateRange = () => {
- const now = Date.now();
-
- return {
- from: new Date(now - DEFAULT_FROM_IN_MILLISECONDS).toISOString(),
- to: new Date(now).toISOString(),
- };
-};
-
-export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderProps) {
+export function useDateRangeProvider({
+ initialDateRange = getDefaultDateRange(),
+}: UseDateRangeProviderProps) {
const [urlState, setUrlState] = useAssetDetailsUrlState();
const dateRange: TimeRange = urlState?.dateRange ?? initialDateRange;
const [parsedDateRange, setParsedDateRange] = useState(parseDateRange(dateRange));
@@ -36,7 +28,7 @@ export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderP
useEffectOnce(() => {
const { from, to } = getParsedDateRange();
- // forces the date picker to initiallize with absolute dates.
+ // forces the date picker to initialize with absolute dates.
setUrlState({ dateRange: { from, to } });
});
@@ -56,9 +48,10 @@ export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderP
return { from, to };
}, [parsedDateRange]);
- const getDateRangeInTimestamp = useCallback(() => {
- return toTimestampRange(getParsedDateRange());
- }, [getParsedDateRange]);
+ const getDateRangeInTimestamp = useCallback(
+ () => toTimestampRange(getParsedDateRange()),
+ [getParsedDateRange]
+ );
return {
dateRange,
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts
index 6fc1991fcb1dc..b19840f7e3ae6 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts
@@ -5,16 +5,19 @@
* 2.0.
*/
+import { useEffect } from 'react';
import createContainer from 'constate';
import { findInventoryModel } from '../../../../common/inventory_models';
import { useSourceContext } from '../../../containers/metrics_source';
import { useMetadata } from './use_metadata';
import { AssetDetailsProps } from '../types';
import { useDateRangeProviderContext } from './use_date_range';
+import { useAssetDetailsUrlState } from './use_asset_details_url_state';
export type UseMetadataProviderProps = Pick;
export function useMetadataProvider({ asset, assetType }: UseMetadataProviderProps) {
+ const [, setUrlState] = useAssetDetailsUrlState();
const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const inventoryModel = findInventoryModel(assetType);
const { sourceId } = useSourceContext();
@@ -27,6 +30,12 @@ export function useMetadataProvider({ asset, assetType }: UseMetadataProviderPro
getDateRangeInTimestamp()
);
+ useEffect(() => {
+ if (metadata?.name) {
+ setUrlState({ name: metadata.name });
+ }
+ }, [metadata?.name, setUrlState]);
+
return {
loading,
error,
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx b/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx
index 6e0cbf9ad2150..987adf78c33dd 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx
@@ -21,9 +21,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { APM_HOST_FILTER_FIELD } from '../constants';
import { LinkToAlertsRule, LinkToApmServices, LinkToNodeDetails } from '../links';
-import { FlyoutTabIds, type RouteState, type LinkOptions, type Tab, type TabIds } from '../types';
+import { ContentTabIds, type RouteState, type LinkOptions, type Tab, type TabIds } from '../types';
import { useAssetDetailsRenderPropsContext } from './use_asset_details_render_props';
-import { useDateRangeProviderContext } from './use_date_range';
import { useTabSwitcherContext } from './use_tab_switcher';
type TabItem = NonNullable['tabs']>[number];
@@ -89,22 +88,15 @@ export const useTemplateHeaderBreadcrumbs = () => {
};
const useRightSideItems = (links?: LinkOptions[]) => {
- const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const { asset, assetType, overrides } = useAssetDetailsRenderPropsContext();
const topCornerLinkComponents: Record = useMemo(
() => ({
- nodeDetails: (
-
- ),
+ nodeDetails: ,
alertRule: ,
apmServices: ,
}),
- [asset, assetType, getDateRangeInTimestamp, overrides?.alertRule?.onCreateRuleClick]
+ [asset, assetType, overrides?.alertRule?.onCreateRuleClick]
);
const rightSideItems = useMemo(
@@ -157,7 +149,7 @@ const useTabs = (tabs: Tab[]) => {
const tabEntries: TabItem[] = useMemo(
() =>
tabs.map(({ name, ...tab }) => {
- if (tab.id === FlyoutTabIds.LINK_TO_APM) {
+ if (tab.id === ContentTabIds.LINK_TO_APM) {
return getTabToApmTraces(name);
}
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx b/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx
index 5de6383099e1d..9ab02ce3f5fd6 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx
@@ -8,7 +8,7 @@
import createContainer from 'constate';
import { useLazyRef } from '../../../hooks/use_lazy_ref';
import type { TabIds } from '../types';
-import { AssetDetailsState, useAssetDetailsUrlState } from './use_asset_details_url_state';
+import { AssetDetailsUrlState, useAssetDetailsUrlState } from './use_asset_details_url_state';
interface TabSwitcherParams {
defaultActiveTabId?: TabIds;
@@ -26,7 +26,7 @@ export function useTabSwitcher({ defaultActiveTabId }: TabSwitcherParams) {
// On a tab click, mark the tab content as allowed to be rendered
renderedTabsSet.current.add(tabId);
- setUrlState({ tabId: tabId as AssetDetailsState['tabId'] });
+ setUrlState({ tabId: tabId as AssetDetailsUrlState['tabId'] });
};
return {
diff --git a/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx b/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx
index 87297b247bb35..2670af9272a31 100644
--- a/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx
@@ -8,29 +8,32 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButtonEmpty } from '@elastic/eui';
import { useLinkProps } from '@kbn/observability-shared-plugin/public';
+import { parse } from '@kbn/datemath';
import { useNodeDetailsRedirect } from '../../../pages/link_to';
+import { Asset } from '../types';
import type { InventoryItemType } from '../../../../common/inventory_models/types';
+import { useAssetDetailsUrlState } from '../hooks/use_asset_details_url_state';
export interface LinkToNodeDetailsProps {
- dateRangeTimestamp: { from: number; to: number };
asset: Asset;
assetType: InventoryItemType;
}
-export const LinkToNodeDetails = ({
- asset,
- assetType,
- dateRangeTimestamp,
-}: LinkToNodeDetailsProps) => {
+export const LinkToNodeDetails = ({ asset, assetType }: LinkToNodeDetailsProps) => {
+ const [state] = useAssetDetailsUrlState();
const { getNodeDetailUrl } = useNodeDetailsRedirect();
+
+ const { dateRange, ...assetDetails } = state ?? {};
+
const nodeDetailMenuItemLinkProps = useLinkProps({
...getNodeDetailUrl({
- nodeType: assetType,
- nodeId: asset.id,
+ assetType,
+ assetId: asset.id,
search: {
- from: dateRangeTimestamp.from,
- to: dateRangeTimestamp.to,
- assetName: asset.name,
+ name: asset.name,
+ ...assetDetails,
+ from: parse(dateRange?.from ?? '')?.valueOf(),
+ to: parse(dateRange?.to ?? '')?.valueOf(),
},
}),
});
diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx
index 72db14e71519c..0a3b29a75ad16 100644
--- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx
@@ -19,7 +19,7 @@ import {
import type { InfraMetadata } from '../../../../../../common/http_api';
import { NOT_AVAILABLE_LABEL } from '../../../translations';
import { useTabSwitcherContext } from '../../../hooks/use_tab_switcher';
-import { FlyoutTabIds } from '../../../types';
+import { ContentTabIds } from '../../../types';
import { ExpandableContent } from '../../../components/expandable_content';
import { MetadataHeader } from './metadata_header';
import { MetadataExplanationMessage } from '../../../components/metadata_explanation';
@@ -76,7 +76,7 @@ const MetadataSummaryListWrapper = ({
const { showTab } = useTabSwitcherContext();
const onClick = () => {
- showTab(FlyoutTabIds.METADATA);
+ showTab(ContentTabIds.METADATA);
};
return (
diff --git a/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx b/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx
index f45aca765610d..f8b4272dae0e6 100644
--- a/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx
@@ -7,7 +7,7 @@
import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import React from 'react';
+import React, { useCallback } from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { InfraLoadingPanel } from '../../loading';
@@ -15,6 +15,7 @@ import { ASSET_DETAILS_FLYOUT_COMPONENT_NAME } from '../constants';
import { Content } from '../content/content';
import { FlyoutHeader } from '../header/flyout_header';
import { useAssetDetailsRenderPropsContext } from '../hooks/use_asset_details_render_props';
+import { useAssetDetailsUrlState } from '../hooks/use_asset_details_url_state';
import { usePageHeader } from '../hooks/use_page_header';
import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
import type { ContentTemplateProps } from '../types';
@@ -23,6 +24,7 @@ export const Flyout = ({
header: { tabs = [], links = [] },
closeFlyout,
}: ContentTemplateProps & { closeFlyout: () => void }) => {
+ const [, setUrlState] = useAssetDetailsUrlState();
const { asset, assetType, loading } = useAssetDetailsRenderPropsContext();
const { rightSideItems, tabEntries } = usePageHeader(tabs, links);
const { activeTabId } = useTabSwitcherContext();
@@ -38,9 +40,14 @@ export const Flyout = ({
});
});
+ const handleOnClose = useCallback(() => {
+ closeFlyout();
+ setUrlState(null);
+ }, [closeFlyout, setUrlState]);
+
return (
return { from: fromTs, to: toTs };
};
+
+const DEFAULT_FROM_IN_MILLISECONDS = 15 * 60000;
+export const getDefaultDateRange = () => {
+ const now = Date.now();
+
+ return {
+ from: new Date(now - DEFAULT_FROM_IN_MILLISECONDS).toISOString(),
+ to: new Date(now).toISOString(),
+ };
+};
diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx
index fd8868f0218af..59a9cad5d672f 100644
--- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx
+++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx
@@ -31,11 +31,12 @@ export const MetricsNodeDetailsLink = ({
const { getNodeDetailUrl } = useNodeDetailsRedirect();
const linkProps = useLinkProps(
getNodeDetailUrl({
- nodeType,
- nodeId: id,
+ assetType: nodeType,
+ assetId: id,
search: {
from: parse(timerange.from)?.valueOf(),
to: parse(timerange.to)?.valueOf(),
+ name: id,
},
})
);
diff --git a/x-pack/plugins/infra/public/pages/link_to/index.ts b/x-pack/plugins/infra/public/pages/link_to/index.ts
index aae22218a4f83..41dc4177186ee 100644
--- a/x-pack/plugins/infra/public/pages/link_to/index.ts
+++ b/x-pack/plugins/infra/public/pages/link_to/index.ts
@@ -9,4 +9,9 @@ export { LinkToLogsPage } from './link_to_logs';
export { LinkToMetricsPage } from './link_to_metrics';
export { RedirectToNodeLogs } from './redirect_to_node_logs';
export { RedirectToNodeDetail } from './redirect_to_node_detail';
+
export { useNodeDetailsRedirect } from './use_node_details_redirect';
+export type {
+ AssetDetailsQueryParams,
+ MetricDetailsQueryParams,
+} from './use_node_details_redirect';
diff --git a/x-pack/plugins/infra/public/pages/link_to/query_params.ts b/x-pack/plugins/infra/public/pages/link_to/query_params.ts
index a80f163993588..45e1bc9a7991d 100644
--- a/x-pack/plugins/infra/public/pages/link_to/query_params.ts
+++ b/x-pack/plugins/infra/public/pages/link_to/query_params.ts
@@ -28,13 +28,3 @@ export const getFromFromLocation = (location: Location) => {
const timeParam = getParamFromQueryString(getQueryStringFromLocation(location), 'from');
return timeParam ? parseFloat(timeParam) : NaN;
};
-
-export const getNodeNameFromLocation = (location: Location) => {
- const nameParam = getParamFromQueryString(getQueryStringFromLocation(location), 'assetName');
- return nameParam;
-};
-
-export const getStateFromLocation = (location: Location) => {
- const nameParam = getParamFromQueryString(getQueryStringFromLocation(location), 'state');
- return nameParam;
-};
diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx
index 1bd8aa3b793c5..606ab658bf648 100644
--- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx
+++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx
@@ -7,15 +7,40 @@
import React from 'react';
import { Redirect, useLocation, useRouteMatch } from 'react-router-dom';
+import rison from '@kbn/rison';
+import { replaceStateKeyInQueryString } from '../../../common/url_state_storage_service';
import { replaceMetricTimeInQueryString } from '../metrics/metric_detail/hooks/use_metrics_time';
-import {
- getFromFromLocation,
- getToFromLocation,
- getNodeNameFromLocation,
- getStateFromLocation,
-} from './query_params';
import { InventoryItemType } from '../../../common/inventory_models/types';
-import { RouteState } from '../../components/asset_details/types';
+import { AssetDetailsUrlState } from '../../components/asset_details/types';
+import { ASSET_DETAILS_URL_STATE_KEY } from '../../components/asset_details/constants';
+
+export const REDIRECT_NODE_DETAILS_FROM_KEY = 'from';
+export const REDIRECT_NODE_DETAILS_TO_KEY = 'to';
+export const REDIRECT_ASSET_DETAILS_KEY = 'assetDetails';
+
+const getHostDetailSearch = (queryParams: URLSearchParams) => {
+ const from = queryParams.get(REDIRECT_NODE_DETAILS_FROM_KEY);
+ const to = queryParams.get(REDIRECT_NODE_DETAILS_TO_KEY);
+ const assetDetailsParam = queryParams.get(REDIRECT_ASSET_DETAILS_KEY);
+
+ return replaceStateKeyInQueryString(ASSET_DETAILS_URL_STATE_KEY, {
+ ...(assetDetailsParam ? (rison.decode(assetDetailsParam) as AssetDetailsUrlState) : undefined),
+ dateRange: {
+ from: from ? new Date(parseFloat(from)).toISOString() : undefined,
+ to: to ? new Date(parseFloat(to)).toISOString() : undefined,
+ },
+ } as AssetDetailsUrlState)('');
+};
+
+const getNodeDetailSearch = (queryParams: URLSearchParams) => {
+ const from = queryParams.get(REDIRECT_NODE_DETAILS_FROM_KEY);
+ const to = queryParams.get(REDIRECT_NODE_DETAILS_TO_KEY);
+
+ return replaceMetricTimeInQueryString(
+ from ? parseFloat(from) : NaN,
+ to ? parseFloat(to) : NaN
+ )('');
+};
export const RedirectToNodeDetail = () => {
const {
@@ -23,35 +48,17 @@ export const RedirectToNodeDetail = () => {
} = useRouteMatch<{ nodeType: InventoryItemType; nodeId: string }>();
const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
- const searchString = replaceMetricTimeInQueryString(
- getFromFromLocation(location),
- getToFromLocation(location)
- )('');
-
- const queryParams = new URLSearchParams(searchString);
-
- if (nodeType === 'host') {
- const assetName = getNodeNameFromLocation(location);
- if (assetName) {
- queryParams.set('assetName', assetName);
- }
- }
-
- let state: RouteState | undefined;
- try {
- const stateFromLocation = getStateFromLocation(location);
- state = stateFromLocation ? JSON.parse(stateFromLocation) : undefined;
- } catch (err) {
- state = undefined;
- }
+ const search =
+ nodeType === 'host' ? getHostDetailSearch(queryParams) : getNodeDetailSearch(queryParams);
return (
);
diff --git a/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.test.tsx b/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.test.tsx
index 81b13a652b7eb..15d8309598945 100644
--- a/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.test.tsx
+++ b/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.test.tsx
@@ -26,7 +26,7 @@ const wrapper = ({ children }: { children: React.ReactNode }): JSX.Element => (
);
describe('useNodeDetailsRedirect', () => {
- it('should return the LinkProperties', () => {
+ it('should return the LinkProperties for assetType pod', () => {
const { result } = renderHook(() => useNodeDetailsRedirect(), { wrapper });
const fromDateStrig = '2019-01-01T11:00:00Z';
@@ -34,8 +34,8 @@ describe('useNodeDetailsRedirect', () => {
expect(
result.current.getNodeDetailUrl({
- nodeType: 'host',
- nodeId: 'example-01',
+ assetType: 'pod',
+ assetId: 'example-01',
search: {
from: new Date(fromDateStrig).getTime(),
to: new Date(toDateStrig).getTime(),
@@ -43,8 +43,37 @@ describe('useNodeDetailsRedirect', () => {
})
).toStrictEqual({
app: 'metrics',
- pathname: 'link-to/host-detail/example-01',
+ pathname: 'link-to/pod-detail/example-01',
search: { from: '1546340400000', to: '1546344000000' },
+ state: {},
+ });
+ });
+
+ it('should return the LinkProperties for assetType host', () => {
+ const { result } = renderHook(() => useNodeDetailsRedirect(), { wrapper });
+
+ const fromDateStrig = '2019-01-01T11:00:00Z';
+ const toDateStrig = '2019-01-01T12:00:00Z';
+
+ expect(
+ result.current.getNodeDetailUrl({
+ assetType: 'host',
+ assetId: 'example-01',
+ search: {
+ from: new Date(fromDateStrig).getTime(),
+ to: new Date(toDateStrig).getTime(),
+ name: 'example-01',
+ },
+ })
+ ).toStrictEqual({
+ app: 'metrics',
+ pathname: 'link-to/host-detail/example-01',
+ search: {
+ from: '1546340400000',
+ to: '1546344000000',
+ assetDetails: '(name:example-01)',
+ },
+ state: {},
});
});
});
diff --git a/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.ts b/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.ts
index ea4726b2e6816..22412fa2064a1 100644
--- a/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.ts
+++ b/x-pack/plugins/infra/public/pages/link_to/use_node_details_redirect.ts
@@ -9,14 +9,32 @@ import { useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import type { LinkDescriptor } from '@kbn/observability-shared-plugin/public';
import useObservable from 'react-use/lib/useObservable';
+import rison from '@kbn/rison';
import type { InventoryItemType } from '../../../common/inventory_models/types';
-import type { RouteState } from '../../components/asset_details/types';
+import type { AssetDetailsUrlState, RouteState } from '../../components/asset_details/types';
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
+import {
+ REDIRECT_NODE_DETAILS_FROM_KEY,
+ REDIRECT_NODE_DETAILS_TO_KEY,
+ REDIRECT_ASSET_DETAILS_KEY,
+} from './redirect_to_node_detail';
-interface QueryParams {
+export interface MetricDetailsQueryParams {
from?: number;
to?: number;
- assetName?: string;
+}
+
+export type AssetDetailsQueryParams = MetricDetailsQueryParams &
+ Omit;
+
+type SearchParams = T extends 'host'
+ ? AssetDetailsQueryParams
+ : MetricDetailsQueryParams;
+
+export interface NodeDetailsRedirectParams {
+ assetType: T;
+ assetId: string;
+ search: SearchParams;
}
export const useNodeDetailsRedirect = () => {
@@ -29,42 +47,36 @@ export const useNodeDetailsRedirect = () => {
const appId = useObservable(currentAppId$);
const getNodeDetailUrl = useCallback(
- ({
- nodeType,
- nodeId,
+ ({
+ assetType,
+ assetId,
search,
- }: {
- nodeType: InventoryItemType;
- nodeId: string;
- search: QueryParams;
- }): LinkDescriptor => {
- const { to, from, ...rest } = search;
+ }: NodeDetailsRedirectParams): LinkDescriptor => {
+ const { from, to, ...additionalParams } = search;
return {
app: 'metrics',
- pathname: `link-to/${nodeType}-detail/${nodeId}`,
+ pathname: `link-to/${assetType}-detail/${assetId}`,
search: {
- ...rest,
- ...(to && from
- ? {
- to: `${to}`,
- from: `${from}`,
- }
+ ...(Object.keys(additionalParams).length > 0
+ ? { [REDIRECT_ASSET_DETAILS_KEY]: rison.encodeUnknown(additionalParams) }
: undefined),
- // While we don't have a shared state between all page in infra, this makes it possible to restore a page state when returning to the previous route
- ...(location.search || location.pathname
- ? {
- state: JSON.stringify({
- originAppId: appId,
- originSearch: location.search,
- originPathname: location.pathname,
- } as RouteState),
- }
+ // retrocompatibility
+ ...(from ? { [REDIRECT_NODE_DETAILS_FROM_KEY]: `${from}` } : undefined),
+ ...(to ? { [REDIRECT_NODE_DETAILS_TO_KEY]: `${to}` } : undefined),
+ },
+ state: {
+ ...(location.key
+ ? ({
+ originAppId: appId,
+ originSearch: location.search,
+ originPathname: location.pathname,
+ } as RouteState)
: undefined),
},
};
},
- [location.pathname, appId, location.search]
+ [location.key, location.search, location.pathname, appId]
);
return { getNodeDetailUrl };
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts
index 7d354d19bed12..6ce0eeddbcb00 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts
@@ -6,41 +6,41 @@
*/
import { i18n } from '@kbn/i18n';
-import { FlyoutTabIds, type Tab } from '../../../../../components/asset_details/types';
+import { ContentTabIds, type Tab } from '../../../../../components/asset_details/types';
export const orderedFlyoutTabs: Tab[] = [
{
- id: FlyoutTabIds.OVERVIEW,
+ id: ContentTabIds.OVERVIEW,
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
defaultMessage: 'Overview',
}),
},
{
- id: FlyoutTabIds.METADATA,
+ id: ContentTabIds.METADATA,
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', {
defaultMessage: 'Metadata',
}),
},
{
- id: FlyoutTabIds.PROCESSES,
+ id: ContentTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
},
{
- id: FlyoutTabIds.LOGS,
+ id: ContentTabIds.LOGS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.logs.title', {
defaultMessage: 'Logs',
}),
},
{
- id: FlyoutTabIds.ANOMALIES,
+ id: ContentTabIds.ANOMALIES,
name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', {
defaultMessage: 'Anomalies',
}),
},
{
- id: FlyoutTabIds.OSQUERY,
+ id: ContentTabIds.OSQUERY,
name: i18n.translate('xpack.infra.nodeDetails.tabs.osquery', {
defaultMessage: 'Osquery',
}),
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/table/entry_title.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/table/entry_title.tsx
index 2019d2efa1c4e..b2fdcc1a1a734 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/table/entry_title.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/table/entry_title.tsx
@@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiToolTip, IconType } fro
import { useLinkProps } from '@kbn/observability-shared-plugin/public';
import { useNodeDetailsRedirect } from '../../../../link_to';
import type { CloudProvider, HostNodeRow } from '../../hooks/use_hosts_table';
+import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
const cloudIcons: Record = {
gcp: 'logoGCP',
@@ -19,22 +20,22 @@ const cloudIcons: Record = {
interface EntryTitleProps {
onClick: () => void;
- dateRangeTs: { from: number; to: number };
title: HostNodeRow['title'];
}
-export const EntryTitle = ({ onClick, dateRangeTs, title }: EntryTitleProps) => {
+export const EntryTitle = ({ onClick, title }: EntryTitleProps) => {
const { name, cloudProvider } = title;
const { getNodeDetailUrl } = useNodeDetailsRedirect();
+ const { parsedDateRange } = useUnifiedSearchContext();
const link = useLinkProps({
...getNodeDetailUrl({
- nodeId: name,
- nodeType: 'host',
+ assetId: name,
+ assetType: 'host',
search: {
- from: dateRangeTs.from,
- to: dateRangeTs.to,
- assetName: name,
+ from: parsedDateRange?.from ? new Date(parsedDateRange?.from).getTime() : undefined,
+ to: parsedDateRange?.to ? new Date(parsedDateRange.to).getTime() : undefined,
+ name,
},
}),
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
index f70718fa855a6..b74c2321859ec 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
@@ -31,7 +31,6 @@ import { ColumnHeader } from '../components/table/column_header';
import { TABLE_COLUMN_LABEL } from '../translations';
import { METRICS_TOOLTIP } from '../../../../common/visualizations';
import { buildCombinedHostsFilter } from '../../../../utils/filters/build';
-import { useUnifiedSearchContext } from './use_unified_search';
/**
* Columns and items types
@@ -127,7 +126,7 @@ const sortTableData =
export const useHostsTable = () => {
const [selectedItems, setSelectedItems] = useState([]);
const { hostNodes } = useHostsViewContext();
- const { getDateRangeAsTimestamp } = useUnifiedSearchContext();
+
const [{ detailsItemId, pagination, sorting }, setProperties] = useHostsTableUrlState();
const {
services: {
@@ -234,11 +233,7 @@ export const useHostsTable = () => {
truncateText: true,
'data-test-subj': 'hostsView-tableRow-title',
render: (title: HostNodeRow['title']) => (
- reportHostEntryClick(title)}
- />
+ reportHostEntryClick(title)} />
),
width: '20%',
},
@@ -303,7 +298,7 @@ export const useHostsTable = () => {
),
field: 'diskSpaceUsage',
@@ -343,7 +338,7 @@ export const useHostsTable = () => {
width: '120px',
},
],
- [detailsItemId, getDateRangeAsTimestamp, reportHostEntryClick, setProperties]
+ [detailsItemId, reportHostEntryClick, setProperties]
);
const selection: EuiTableSelectionType = {
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/translations.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/translations.ts
index 9a2860b224031..304b1980d965c 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/translations.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/translations.ts
@@ -16,8 +16,8 @@ export const TABLE_COLUMN_LABEL = {
defaultMessage: 'CPU usage (avg.)',
}),
- diskSpaceUsage: i18n.translate('xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader', {
- defaultMessage: 'Disk Space Usage (avg.)',
+ diskSpaceUsage: i18n.translate('xpack.infra.hostsViewPage.table.diskUsageColumnHeader', {
+ defaultMessage: 'Disk Usage (avg.)',
}),
tx: i18n.translate('xpack.infra.hostsViewPage.table.txColumnHeader', {
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index bf854fb546ae1..562c7a43175cc 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -22,7 +22,7 @@ import { MetricsExplorerOptionsContainer } from './metrics_explorer/hooks/use_me
import { WithMetricsExplorerOptionsUrlState } from '../../containers/metrics_explorer/with_metrics_explorer_options_url_state';
import { MetricsExplorerPage } from './metrics_explorer';
import { SnapshotPage } from './inventory_view';
-import { MetricDetail } from './metric_detail';
+import { NodeDetail } from './metric_detail';
import { MetricsSettingsPage } from './settings';
import { HostsLandingPage } from './hosts/hosts_landing_page';
import { SourceLoadingPage } from '../../components/source_loading_page';
@@ -109,7 +109,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
)}
-
+
} />
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx
index ef69c1d104062..7614b46014378 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx
@@ -80,12 +80,12 @@ export const NodeContextPopover = ({
const nodeDetailMenuItemLinkProps = useLinkProps({
...getNodeDetailUrl({
- nodeType,
- nodeId: node.id,
+ assetType: nodeType,
+ assetId: node.id,
search: {
from: nodeDetailFrom,
to: currentTime,
- assetName: node.name,
+ name: node.name,
},
}),
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx
index fec97c5fc0720..1aef6ff563192 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx
@@ -78,12 +78,12 @@ export const NodeContextMenu: React.FC = withTheme
const nodeDetailMenuItemLinkProps = useLinkProps({
...getNodeDetailUrl({
- nodeType,
- nodeId: node.id,
+ assetType: node.type,
+ assetId: node.id,
search: {
from: nodeDetailFrom,
to: currentTime,
- assetName: node.name,
+ name: node.name,
},
}),
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx
index 10af65db3fdd9..d5ba376a514bb 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx
@@ -5,53 +5,51 @@
* 2.0.
*/
-import React, { useMemo } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
+import React from 'react';
+import { useRouteMatch } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
-import type { TimeRange } from '@kbn/es-query';
import { NoRemoteCluster } from '../../../components/empty_states';
import { SourceErrorPage } from '../../../components/source_error_page';
import { SourceLoadingPage } from '../../../components/source_loading_page';
import { useSourceContext } from '../../../containers/metrics_source';
-import { FlyoutTabIds, type Tab } from '../../../components/asset_details/types';
+import { ContentTabIds, type Tab } from '../../../components/asset_details/types';
import type { InventoryItemType } from '../../../../common/inventory_models/types';
import { AssetDetails } from '../../../components/asset_details/asset_details';
import { MetricsPageTemplate } from '../page_template';
-import { useMetricsTimeContext } from './hooks/use_metrics_time';
const orderedFlyoutTabs: Tab[] = [
{
- id: FlyoutTabIds.OVERVIEW,
+ id: ContentTabIds.OVERVIEW,
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
defaultMessage: 'Overview',
}),
},
{
- id: FlyoutTabIds.METADATA,
+ id: ContentTabIds.METADATA,
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', {
defaultMessage: 'Metadata',
}),
},
{
- id: FlyoutTabIds.PROCESSES,
+ id: ContentTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
},
{
- id: FlyoutTabIds.LOGS,
+ id: ContentTabIds.LOGS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.logs.title', {
defaultMessage: 'Logs',
}),
},
{
- id: FlyoutTabIds.ANOMALIES,
+ id: ContentTabIds.ANOMALIES,
name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', {
defaultMessage: 'Anomalies',
}),
},
{
- id: FlyoutTabIds.OSQUERY,
+ id: ContentTabIds.OSQUERY,
name: i18n.translate('xpack.infra.nodeDetails.tabs.osquery', {
defaultMessage: 'Osquery',
}),
@@ -63,25 +61,6 @@ export const AssetDetailPage = () => {
const {
params: { type: nodeType, node: nodeId },
} = useRouteMatch<{ type: InventoryItemType; node: string }>();
- const { search } = useLocation();
-
- const assetName = useMemo(() => {
- const queryParams = new URLSearchParams(search);
- return queryParams.get('assetName') ?? undefined;
- }, [search]);
-
- const { timeRange } = useMetricsTimeContext();
-
- const dateRange: TimeRange = useMemo(
- () => ({
- from:
- typeof timeRange.from === 'number'
- ? new Date(timeRange.from).toISOString()
- : timeRange.from,
- to: typeof timeRange.to === 'number' ? new Date(timeRange.to).toISOString() : timeRange.to,
- }),
- [timeRange.from, timeRange.to]
- );
const { metricIndicesExist, remoteClustersExist } = source?.status ?? {};
@@ -110,10 +89,8 @@ export const AssetDetailPage = () => {
{
+export const NodeDetail = () => {
const {
params: { type: nodeType, node: nodeName },
} = useRouteMatch<{ type: InventoryItemType; node: string }>();
@@ -27,9 +27,13 @@ export const MetricDetail = () => {
return (
-
- {nodeType === 'host' ? : }
-
+ {nodeType === 'host' ? (
+
+ ) : (
+
+
+
+ )}
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx
deleted file mode 100644
index 43009ad6b8ff9..0000000000000
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx
+++ /dev/null
@@ -1,21 +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 { EuiErrorBoundary } from '@elastic/eui';
-import React from 'react';
-import { MetricsTimeProvider } from './hooks/use_metrics_time';
-
-export const withMetricPageProviders =
- (Component: React.ComponentType) =>
- (props: T) =>
- (
-
-
-
-
-
- );
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx
index 0888424c8f2db..f52a137b8792c 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx
@@ -105,12 +105,13 @@ export const MetricsExplorerChartContextMenu: React.FC = ({
: [];
const nodeType = source && options.groupBy && fieldToNodeType(source, options.groupBy);
+
const nodeDetailLinkProps = useLinkProps({
app: 'metrics',
...(nodeType
? getNodeDetailUrl({
- nodeType,
- nodeId: series.id,
+ assetType: nodeType,
+ assetId: series.id,
search: {
from: dateMathExpressionToEpoch(timeRange.from),
to: dateMathExpressionToEpoch(timeRange.to, true),
diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc
index a88f6543871e4..5064e06b156e0 100644
--- a/x-pack/plugins/observability/kibana.jsonc
+++ b/x-pack/plugins/observability/kibana.jsonc
@@ -8,6 +8,7 @@
"browser": true,
"configPath": ["xpack", "observability"],
"requiredPlugins": [
+ "aiops",
"alerting",
"cases",
"charts",
diff --git a/x-pack/plugins/observability/public/components/slo/error_rate_chart/error_rate_chart.tsx b/x-pack/plugins/observability/public/components/slo/error_rate_chart/error_rate_chart.tsx
new file mode 100644
index 0000000000000..0c804afb8bea3
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/slo/error_rate_chart/error_rate_chart.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 { ViewMode } from '@kbn/embeddable-plugin/public';
+import { SLOResponse } from '@kbn/slo-schema';
+import moment from 'moment';
+import React from 'react';
+import { useKibana } from '../../../utils/kibana_react';
+import { useLensDefinition } from './use_lens_definition';
+
+interface Props {
+ slo: SLOResponse;
+ fromRange: Date;
+}
+
+export function ErrorRateChart({ slo, fromRange }: Props) {
+ const {
+ lens: { EmbeddableComponent },
+ } = useKibana().services;
+ const lensDef = useLensDefinition(slo);
+
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/slo/error_rate_chart/index.ts b/x-pack/plugins/observability/public/components/slo/error_rate_chart/index.ts
new file mode 100644
index 0000000000000..d5f3e681e1ae6
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/slo/error_rate_chart/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 * from './error_rate_chart';
diff --git a/x-pack/plugins/observability/public/components/slo/error_rate_chart/use_lens_definition.ts b/x-pack/plugins/observability/public/components/slo/error_rate_chart/use_lens_definition.ts
new file mode 100644
index 0000000000000..05f431750400f
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/slo/error_rate_chart/use_lens_definition.ts
@@ -0,0 +1,566 @@
+/*
+ * 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 { useEuiTheme } from '@elastic/eui';
+import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
+import { ALL_VALUE, SLOResponse } from '@kbn/slo-schema';
+
+export function useLensDefinition(slo: SLOResponse): TypedLensByValueInput['attributes'] {
+ const { euiTheme } = useEuiTheme();
+
+ return {
+ title: 'SLO Error Rate',
+ description: '',
+ visualizationType: 'lnsXY',
+ type: 'lens',
+ references: [],
+ state: {
+ visualization: {
+ legend: {
+ isVisible: false,
+ position: 'right',
+ showSingleSeries: false,
+ },
+ valueLabels: 'hide',
+ fittingFunction: 'None',
+ axisTitlesVisibilitySettings: {
+ x: false,
+ yLeft: false,
+ yRight: true,
+ },
+ tickLabelsVisibilitySettings: {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
+ labelsOrientation: {
+ x: 0,
+ yLeft: 0,
+ yRight: 0,
+ },
+ gridlinesVisibilitySettings: {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
+ preferredSeriesType: 'area',
+ layers: [
+ {
+ layerId: '8730e8af-7dac-430e-9cef-3b9989ff0866',
+ accessors: ['9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14'],
+ position: 'top',
+ seriesType: 'area',
+ showGridlines: false,
+ layerType: 'data',
+ xAccessor: '627ded04-eae0-4437-83a1-bbb6138d2c3b',
+ yConfig: [
+ {
+ forAccessor: '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14',
+ color: euiTheme.colors.danger,
+ },
+ ],
+ },
+ {
+ layerId: '34298f84-681e-4fa3-8107-d6facb32ed92',
+ layerType: 'referenceLine',
+ accessors: [
+ '0a42b72b-cd5a-4d59-81ec-847d97c268e6',
+ '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762',
+ 'c531a6b1-70dd-4918-bdd0-a21535a7af05',
+ '61f9e663-10eb-41f7-b584-1f0f95418489',
+ ],
+ yConfig: [
+ {
+ forAccessor: '0a42b72b-cd5a-4d59-81ec-847d97c268e6',
+ axisMode: 'left',
+ textVisibility: true,
+ color: euiTheme.colors.danger,
+ iconPosition: 'right',
+ },
+ {
+ forAccessor: '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762',
+ axisMode: 'left',
+ textVisibility: true,
+ color: euiTheme.colors.danger,
+ iconPosition: 'right',
+ },
+ {
+ forAccessor: 'c531a6b1-70dd-4918-bdd0-a21535a7af05',
+ axisMode: 'left',
+ textVisibility: true,
+ color: euiTheme.colors.danger,
+ iconPosition: 'right',
+ },
+ {
+ forAccessor: '61f9e663-10eb-41f7-b584-1f0f95418489',
+ axisMode: 'left',
+ textVisibility: true,
+ color: euiTheme.colors.danger,
+ iconPosition: 'right',
+ },
+ ],
+ },
+ ],
+ },
+ query: {
+ query: `slo.id : "${slo.id}" and slo.instanceId : "${slo.instanceId ?? ALL_VALUE}"`,
+ language: 'kuery',
+ },
+ filters: [],
+ datasourceStates: {
+ formBased: {
+ layers: {
+ '8730e8af-7dac-430e-9cef-3b9989ff0866': {
+ columns: {
+ '627ded04-eae0-4437-83a1-bbb6138d2c3b': {
+ label: '@timestamp',
+ dataType: 'date',
+ operationType: 'date_histogram',
+ sourceField: '@timestamp',
+ isBucketed: true,
+ scale: 'interval',
+ params: {
+ // @ts-ignore
+ interval: 'auto',
+ includeEmptyRows: true,
+ dropPartials: false,
+ },
+ },
+ ...(slo.budgetingMethod === 'occurrences' && {
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0': {
+ label: 'Part of Error rate',
+ dataType: 'number',
+ operationType: 'sum',
+ sourceField: 'slo.numerator',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ emptyAsNull: false,
+ },
+ customLabel: true,
+ },
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1': {
+ label: 'Part of Error rate',
+ dataType: 'number',
+ operationType: 'sum',
+ sourceField: 'slo.denominator',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ emptyAsNull: false,
+ },
+ customLabel: true,
+ },
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2': {
+ label: 'Part of Error rate',
+ dataType: 'number',
+ operationType: 'math',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ tinymathAst: {
+ type: 'function',
+ name: 'subtract',
+ args: [
+ 1,
+ {
+ type: 'function',
+ name: 'divide',
+ args: [
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1',
+ ],
+ location: {
+ min: 3,
+ max: 47,
+ },
+ text: '(sum(slo.numerator) / sum(slo.denominator))',
+ },
+ ],
+ location: {
+ min: 0,
+ max: 47,
+ },
+ text: '1 - (sum(slo.numerator) / sum(slo.denominator))',
+ },
+ },
+ references: [
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1',
+ ],
+ customLabel: true,
+ },
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14': {
+ label: 'Error rate',
+ dataType: 'number',
+ operationType: 'formula',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ formula: '1 - (sum(slo.numerator) / sum(slo.denominator))',
+ isFormulaBroken: false,
+ format: {
+ id: 'percent',
+ params: {
+ decimals: 2,
+ },
+ },
+ },
+ references: ['9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2'],
+ customLabel: true,
+ },
+ }),
+ ...(slo.budgetingMethod === 'timeslices' && {
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0': {
+ label: 'Part of Error rate',
+ dataType: 'number',
+ operationType: 'sum',
+ sourceField: 'slo.isGoodSlice',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ emptyAsNull: false,
+ },
+ customLabel: true,
+ },
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1': {
+ label: 'Part of Error rate',
+ dataType: 'number',
+ operationType: 'count',
+ sourceField: 'slo.isGoodSlice',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ emptyAsNull: false,
+ },
+ customLabel: true,
+ },
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2': {
+ label: 'Part of Error rate',
+ dataType: 'number',
+ operationType: 'math',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ tinymathAst: {
+ type: 'function',
+ name: 'subtract',
+ args: [
+ 1,
+ {
+ type: 'function',
+ name: 'divide',
+ args: [
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1',
+ ],
+ location: {
+ min: 3,
+ max: 47,
+ },
+ text: '(sum(slo.isGoodSlice) / count(slo.isGoodSlice))',
+ },
+ ],
+ location: {
+ min: 0,
+ max: 47,
+ },
+ text: '1 - (sum(slo.isGoodSlice) / count(slo.isGoodSlice))',
+ },
+ },
+ references: [
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1',
+ ],
+ customLabel: true,
+ },
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14': {
+ label: 'Error rate',
+ dataType: 'number',
+ operationType: 'formula',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ formula: '1 - (sum(slo.isGoodSlice) / count(slo.isGoodSlice))',
+ isFormulaBroken: false,
+ format: {
+ id: 'percent',
+ params: {
+ decimals: 2,
+ },
+ },
+ },
+ references: ['9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2'],
+ customLabel: true,
+ },
+ }),
+ },
+ columnOrder: [
+ '627ded04-eae0-4437-83a1-bbb6138d2c3b',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X0',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X1',
+ '9f69a7b0-34b9-4b76-9ff7-26dc1a06ec14X2',
+ ],
+ incompleteColumns: {},
+ sampling: 1,
+ },
+ '34298f84-681e-4fa3-8107-d6facb32ed92': {
+ linkToLayers: [],
+ columns: {
+ '0a42b72b-cd5a-4d59-81ec-847d97c268e6X0': {
+ label: 'Part of 14.4x',
+ dataType: 'number',
+ operationType: 'math',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ tinymathAst: {
+ type: 'function',
+ name: 'multiply',
+ args: [
+ {
+ type: 'function',
+ name: 'subtract',
+ args: [1, slo.objective.target],
+ location: {
+ min: 1,
+ max: 9,
+ },
+ text: `1 - ${slo.objective.target}`,
+ },
+ 14.4,
+ ],
+ location: {
+ min: 0,
+ max: 17,
+ },
+ text: `(1 - ${slo.objective.target}) * 14.4`,
+ },
+ },
+ references: [],
+ customLabel: true,
+ },
+ '0a42b72b-cd5a-4d59-81ec-847d97c268e6': {
+ label: '14.4x',
+ dataType: 'number',
+ operationType: 'formula',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ formula: `(1 - ${slo.objective.target}) * 14.4`,
+ isFormulaBroken: false,
+ },
+ references: ['0a42b72b-cd5a-4d59-81ec-847d97c268e6X0'],
+ customLabel: true,
+ },
+ '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762X0': {
+ label: 'Part of 6x',
+ dataType: 'number',
+ operationType: 'math',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ tinymathAst: {
+ type: 'function',
+ name: 'multiply',
+ args: [
+ {
+ type: 'function',
+ name: 'subtract',
+ args: [1, slo.objective.target],
+ location: {
+ min: 1,
+ max: 9,
+ },
+ text: `1 - ${slo.objective.target}`,
+ },
+ 6,
+ ],
+ location: {
+ min: 0,
+ max: 14,
+ },
+ text: `(1 - ${slo.objective.target}) * 6`,
+ },
+ },
+ references: [],
+ customLabel: true,
+ },
+ '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762': {
+ label: '6x',
+ dataType: 'number',
+ operationType: 'formula',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ formula: `(1 - ${slo.objective.target}) * 6`,
+ isFormulaBroken: false,
+ },
+ references: ['76d3bcc9-7d45-4b08-b2b1-8d3866ca0762X0'],
+ customLabel: true,
+ },
+ 'c531a6b1-70dd-4918-bdd0-a21535a7af05X0': {
+ label: 'Part of 3x',
+ dataType: 'number',
+ operationType: 'math',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ tinymathAst: {
+ type: 'function',
+ name: 'multiply',
+ args: [
+ {
+ type: 'function',
+ name: 'subtract',
+ args: [1, slo.objective.target],
+ location: {
+ min: 1,
+ max: 9,
+ },
+ text: `1 - ${slo.objective.target}`,
+ },
+ 3,
+ ],
+ location: {
+ min: 0,
+ max: 14,
+ },
+ text: `(1 - ${slo.objective.target}) * 3`,
+ },
+ },
+ references: [],
+ customLabel: true,
+ },
+ 'c531a6b1-70dd-4918-bdd0-a21535a7af05': {
+ label: '3x',
+ dataType: 'number',
+ operationType: 'formula',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ formula: `(1 - ${slo.objective.target}) * 3`,
+ isFormulaBroken: false,
+ },
+ references: ['c531a6b1-70dd-4918-bdd0-a21535a7af05X0'],
+ customLabel: true,
+ },
+ '61f9e663-10eb-41f7-b584-1f0f95418489X0': {
+ label: 'Part of 1x',
+ dataType: 'number',
+ operationType: 'math',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ tinymathAst: {
+ type: 'function',
+ name: 'multiply',
+ args: [
+ {
+ type: 'function',
+ name: 'subtract',
+ args: [1, slo.objective.target],
+ location: {
+ min: 1,
+ max: 9,
+ },
+ text: `1 - ${slo.objective.target}`,
+ },
+ 1,
+ ],
+ location: {
+ min: 0,
+ max: 14,
+ },
+ text: `(1 - ${slo.objective.target}) * 1`,
+ },
+ },
+ references: [],
+ customLabel: true,
+ },
+ '61f9e663-10eb-41f7-b584-1f0f95418489': {
+ label: '1x',
+ dataType: 'number',
+ operationType: 'formula',
+ isBucketed: false,
+ scale: 'ratio',
+ params: {
+ // @ts-ignore
+ formula: `(1 - ${slo.objective.target}) * 1`,
+ isFormulaBroken: false,
+ },
+ references: ['61f9e663-10eb-41f7-b584-1f0f95418489X0'],
+ customLabel: true,
+ },
+ },
+ columnOrder: [
+ '0a42b72b-cd5a-4d59-81ec-847d97c268e6',
+ '0a42b72b-cd5a-4d59-81ec-847d97c268e6X0',
+ '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762X0',
+ '76d3bcc9-7d45-4b08-b2b1-8d3866ca0762',
+ 'c531a6b1-70dd-4918-bdd0-a21535a7af05X0',
+ 'c531a6b1-70dd-4918-bdd0-a21535a7af05',
+ '61f9e663-10eb-41f7-b584-1f0f95418489X0',
+ '61f9e663-10eb-41f7-b584-1f0f95418489',
+ ],
+ sampling: 1,
+ ignoreGlobalFilters: false,
+ incompleteColumns: {},
+ },
+ },
+ },
+ indexpattern: {
+ layers: {},
+ },
+ textBased: {
+ layers: {},
+ },
+ },
+ internalReferences: [
+ {
+ type: 'index-pattern',
+ id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5',
+ name: 'indexpattern-datasource-layer-8730e8af-7dac-430e-9cef-3b9989ff0866',
+ },
+ {
+ type: 'index-pattern',
+ id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5',
+ name: 'indexpattern-datasource-layer-34298f84-681e-4fa3-8107-d6facb32ed92',
+ },
+ ],
+ adHocDataViews: {
+ '32ca1ad4-81c0-4daf-b9d1-07118044bdc5': {
+ id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5',
+ title: '.slo-observability.sli-v2.*',
+ timeFieldName: '@timestamp',
+ sourceFilters: [],
+ fieldFormats: {},
+ runtimeFieldMap: {},
+ fieldAttrs: {},
+ allowNoIndex: false,
+ name: 'SLO Rollup Data',
+ },
+ },
+ },
+ };
+}
diff --git a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx
index e540572e67bc5..2e035cdcf8fb8 100644
--- a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx
+++ b/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx
@@ -39,6 +39,12 @@ jest.mock('../../../utils/kibana_react', () => ({
services: {
...mockCoreMock.createStart(),
charts: mockedChartStartContract,
+ aiops: {
+ EmbeddableChangePointChart: jest.fn(),
+ },
+ data: {
+ search: jest.fn(),
+ },
},
}),
}));
@@ -47,6 +53,7 @@ describe('AlertDetailsAppSection', () => {
const queryClient = new QueryClient();
const mockedSetAlertSummaryFields = jest.fn();
const ruleLink = 'ruleLink';
+
const renderComponent = () => {
return render(
@@ -69,7 +76,7 @@ describe('AlertDetailsAppSection', () => {
it('should render rule and alert data', async () => {
const result = renderComponent();
- expect((await result.findByTestId('thresholdRuleAppSection')).children.length).toBe(3);
+ expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(3);
expect(result.getByTestId('thresholdRule-2000-2500')).toBeTruthy();
});
@@ -92,9 +99,9 @@ describe('AlertDetailsAppSection', () => {
it('should render annotations', async () => {
const mockedExpressionChart = jest.fn(() => );
(ExpressionChart as jest.Mock).mockImplementation(mockedExpressionChart);
- renderComponent();
+ const alertDetailsAppSectionComponent = renderComponent();
- expect(mockedExpressionChart).toHaveBeenCalledTimes(3);
+ expect(alertDetailsAppSectionComponent.getAllByTestId('ExpressionChart').length).toBe(3);
expect(mockedExpressionChart.mock.calls[0]).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx
index 7498e7c0e8d6e..95367577ccb22 100644
--- a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx
+++ b/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx
@@ -6,27 +6,31 @@
*/
import moment from 'moment';
-import React, { useEffect, useMemo } from 'react';
import { DataViewBase, Query } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
+import React, { useEffect, useMemo, useState } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiPanel,
EuiSpacer,
+ EuiTabbedContent,
+ EuiTabbedContentTab,
EuiText,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils';
-import { Rule } from '@kbn/alerting-plugin/common';
+import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common';
import {
AlertAnnotation,
getPaddedAlertTimeRange,
AlertActiveTimeRangeAnnotation,
} from '@kbn/observability-alert-details';
+import { DataView } from '@kbn/data-views-plugin/common';
+import type { TimeRange } from '@kbn/es-query';
import { useKibana } from '../../../utils/kibana_react';
import { metricValueFormatter } from '../../../../common/threshold_rule/metric_value_formatter';
import { AlertSummaryField, TopAlert } from '../../..';
@@ -36,7 +40,7 @@ import { ExpressionChart } from './expression_chart';
import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart';
import { Threshold } from './threshold';
import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options';
-import { MetricThresholdRuleTypeParams } from '../types';
+import { AlertParams, MetricExpression, MetricThresholdRuleTypeParams } from '../types';
// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690
export type MetricThresholdRule = Rule;
@@ -45,6 +49,8 @@ export type MetricThresholdAlert = TopAlert;
const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm';
const ALERT_START_ANNOTATION_ID = 'alert_start_annotation';
const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation';
+const OVERVIEW_TAB_ID = 'overview';
+const RELATED_EVENTS_TAB_ID = 'relatedEvents';
interface AppSectionProps {
alert: MetricThresholdAlert;
@@ -60,17 +66,12 @@ export default function AlertDetailsAppSection({
ruleLink,
setAlertSummaryFields,
}: AppSectionProps) {
- const { uiSettings, charts } = useKibana().services;
+ const { uiSettings, charts, aiops, data } = useKibana().services;
+ const { EmbeddableChangePointChart } = aiops;
const { euiTheme } = useEuiTheme();
-
- // TODO Use rule data view
- const derivedIndexPattern = useMemo(
- () => ({
- fields: [],
- title: 'unknown-index',
- }),
- []
- );
+ const [dataView, setDataView] = useState();
+ const [, setDataViewError] = useState();
+ const ruleParams = rule.params as RuleTypeParams & AlertParams;
const chartProps = {
theme: charts.theme.useChartsTheme(),
baseTheme: charts.theme.useChartsBaseTheme(),
@@ -111,63 +112,143 @@ export default function AlertDetailsAppSection({
]);
}, [alert, rule, ruleLink, setAlertSummaryFields]);
- return !!rule.params.criteria ? (
-
- {rule.params.criteria.map((criterion, index) => (
-
-
-
-
- {criterion.aggType.toUpperCase()}{' '}
- {'metric' in criterion ? criterion.metric : undefined}
-
-
-
-
-
-
-
-
-
- metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined)
- }
- title={i18n.translate(
- 'xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle',
- {
- defaultMessage: 'Threshold breached',
- }
- )}
- comparator={criterion.comparator}
+ const derivedIndexPattern = useMemo(
+ () => ({
+ fields: dataView?.fields || [],
+ title: dataView?.getIndexPattern() || 'unknown-index',
+ }),
+ [dataView]
+ );
+
+ useEffect(() => {
+ const initDataView = async () => {
+ const ruleSearchConfiguration = ruleParams.searchConfiguration;
+ try {
+ const createdSearchSource = await data.search.searchSource.create(ruleSearchConfiguration);
+ setDataView(createdSearchSource.getField('index'));
+ } catch (error) {
+ setDataViewError(error);
+ }
+ };
+
+ initDataView();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [data.search.searchSource]);
+
+ const relatedEventsTimeRange = (criterion: MetricExpression): TimeRange => {
+ return {
+ from: moment(alert.start)
+ .subtract((criterion.timeSize ?? 5) * 2, criterion.timeUnit ?? 'minutes')
+ .toISOString(),
+ to: moment(alert.lastUpdated).toISOString(),
+ mode: 'absolute',
+ };
+ };
+
+ const overviewTab = !!ruleParams.criteria ? (
+ <>
+
+
+ {ruleParams.criteria.map((criterion, index) => (
+
+
+
+
+ {criterion.aggType.toUpperCase()}{' '}
+ {'metric' in criterion ? criterion.metric : undefined}
+
+
+
+
-
-
-
+
+
+
+
+ metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined)
+ }
+ title={i18n.translate(
+ 'xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle',
+ {
+ defaultMessage: 'Threshold breached',
+ }
+ )}
+ comparator={criterion.comparator}
+ />
+
+
+
+
+
+
+
+ ))}
+
+ >
+ ) : null;
+
+ const relatedEventsTab = !!ruleParams.criteria ? (
+ <>
+
+
+ {ruleParams.criteria.map((criterion, criterionIndex) =>
+ criterion.metrics?.map(
+ (metric, metricIndex) =>
+ dataView &&
+ dataView.id && (
+
-
-
-
-
- ))}
-
+ )
+ )
+ )}
+
+ >
) : null;
+
+ const tabs: EuiTabbedContentTab[] = [
+ {
+ id: OVERVIEW_TAB_ID,
+ name: i18n.translate('xpack.observability.threshold.alertDetails.tab.overviewLabel', {
+ defaultMessage: 'Overview',
+ }),
+ 'data-test-subj': 'overviewTab',
+ content: overviewTab,
+ },
+ {
+ id: RELATED_EVENTS_TAB_ID,
+ name: i18n.translate('xpack.observability.threshold.alertDetails.tab.relatedEventsLabel', {
+ defaultMessage: 'Related Events',
+ }),
+ 'data-test-subj': 'relatedEventsTab',
+ content: relatedEventsTab,
+ },
+ ];
+
+ return ;
}
diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.tsx
new file mode 100644
index 0000000000000..1fd2014dee4c7
--- /dev/null
+++ b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate.tsx
@@ -0,0 +1,110 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTextColor } from '@elastic/eui';
+import numeral from '@elastic/numeral';
+import { i18n } from '@kbn/i18n';
+import { SLOResponse } from '@kbn/slo-schema';
+import moment from 'moment';
+import React from 'react';
+import { toDuration, toMinutes } from '../../../utils/slo/duration';
+
+export interface BurnRateParams {
+ slo: SLOResponse;
+ threshold: number;
+ burnRate?: number;
+ isLoading?: boolean;
+}
+
+type Status = 'NO_DATA' | 'BREACHED' | 'OK';
+
+function getTitleFromStatus(status: Status): string {
+ if (status === 'NO_DATA')
+ return i18n.translate('xpack.observability.slo.burnRate.noDataStatusTitle', {
+ defaultMessage: 'No value',
+ });
+ if (status === 'BREACHED')
+ return i18n.translate('xpack.observability.slo.burnRate.breachedStatustTitle', {
+ defaultMessage: 'Critical value breached',
+ });
+
+ return i18n.translate('xpack.observability.slo.burnRate.okStatusTitle', {
+ defaultMessage: 'Acceptable value',
+ });
+}
+
+function getSubtitleFromStatus(
+ status: Status,
+ burnRate: number | undefined = 1,
+ slo: SLOResponse
+): string {
+ if (status === 'NO_DATA')
+ return i18n.translate('xpack.observability.slo.burnRate.noDataStatusSubtitle', {
+ defaultMessage: 'Waiting for more data.',
+ });
+ if (status === 'BREACHED')
+ return i18n.translate('xpack.observability.slo.burnRate.breachedStatustSubtitle', {
+ defaultMessage: 'At current rate, the error budget will be exhausted in {hour} hours.',
+ values: {
+ hour: numeral(
+ moment
+ .duration(toMinutes(toDuration(slo.timeWindow.duration)) / burnRate, 'minutes')
+ .asHours()
+ ).format('0'),
+ },
+ });
+
+ return i18n.translate('xpack.observability.slo.burnRate.okStatusSubtitle', {
+ defaultMessage: 'There is no risk of error budget exhaustion.',
+ });
+}
+
+export function BurnRate({ threshold, burnRate, slo, isLoading }: BurnRateParams) {
+ const status: Status = !burnRate ? 'NO_DATA' : burnRate > threshold ? 'BREACHED' : 'OK';
+ const color = status === 'NO_DATA' ? 'subdued' : status === 'BREACHED' ? 'danger' : 'success';
+
+ return (
+
+
+
+
+
+ {getTitleFromStatus(status)}
+
+
+
+
+ {getSubtitleFromStatus(status, burnRate, slo)}
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.observability.slo.burnRate.threshold', {
+ defaultMessage: 'Threshold is {threshold}x',
+ values: { threshold },
+ })}
+
+
+ }
+ />
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate_window.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate_window.tsx
deleted file mode 100644
index 24d3a4e14468f..0000000000000
--- a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rate_window.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import {
- EuiSpacer,
- EuiFlexGroup,
- EuiPanel,
- EuiFlexItem,
- EuiStat,
- EuiTextColor,
- EuiText,
- EuiIconTip,
-} from '@elastic/eui';
-import numeral from '@elastic/numeral';
-import { i18n } from '@kbn/i18n';
-
-export interface BurnRateWindowParams {
- title: string;
- target: number;
- longWindow: {
- label: string;
- burnRate: number | null;
- sli: number | null;
- };
- shortWindow: {
- label: string;
- burnRate: number | null;
- sli: number | null;
- };
- isLoading?: boolean;
- size?: 'xxxs' | 'xxs' | 'xs' | 's' | 'm' | 'l';
-}
-
-const SUBDUED = 'subdued';
-const DANGER = 'danger';
-const SUCCESS = 'success';
-const WARNING = 'warning';
-
-function getColorBasedOnBurnRate(target: number, burnRate: number | null, sli: number | null) {
- if (burnRate === null || sli === null || sli < 0) {
- return SUBDUED;
- }
- if (burnRate > target) {
- return DANGER;
- }
- return SUCCESS;
-}
-
-export function BurnRateWindow({
- title,
- target,
- longWindow,
- shortWindow,
- isLoading,
- size = 's',
-}: BurnRateWindowParams) {
- const longWindowColor = getColorBasedOnBurnRate(target, longWindow.burnRate, longWindow.sli);
- const shortWindowColor = getColorBasedOnBurnRate(target, shortWindow.burnRate, shortWindow.sli);
-
- const overallColor =
- longWindowColor === DANGER && shortWindowColor === DANGER
- ? DANGER
- : [longWindowColor, shortWindowColor].includes(DANGER)
- ? WARNING
- : longWindowColor === SUBDUED && shortWindowColor === SUBDUED
- ? SUBDUED
- : SUCCESS;
-
- const isLongWindowValid =
- longWindow.burnRate != null && longWindow.sli != null && longWindow.sli >= 0;
-
- const isShortWindowValid =
- shortWindow.burnRate != null && shortWindow.sli != null && shortWindow.sli >= 0;
-
- return (
-
-
-
- {title}
-
-
-
-
-
-
-
- {longWindow.label}
-
- }
- />
-
-
-
- {shortWindow.label}
-
- }
- />
-
-
-
- );
-}
diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx
index 9ea2c0c531087..54ee0dd11cf76 100644
--- a/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_details/components/burn_rates.tsx
@@ -5,53 +5,78 @@
* 2.0.
*/
-import React from 'react';
-import { GetSLOBurnRatesResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
import {
- EuiSpacer,
- EuiFlexGrid,
- EuiPanel,
- EuiTitle,
+ EuiBetaBadge,
+ EuiButtonGroup,
EuiFlexGroup,
EuiFlexItem,
- EuiBetaBadge,
+ EuiPanel,
+ EuiTitle,
+ htmlIdGenerator,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { SLOWithSummaryResponse } from '@kbn/slo-schema';
+import moment from 'moment';
+import React, { useEffect, useState } from 'react';
+import { ErrorRateChart } from '../../../components/slo/error_rate_chart';
import { useFetchSloBurnRates } from '../../../hooks/slo/use_fetch_slo_burn_rates';
-import { BurnRateWindow, BurnRateWindowParams } from './burn_rate_window';
+import { BurnRate } from './burn_rate';
interface Props {
slo: SLOWithSummaryResponse;
isAutoRefreshing?: boolean;
}
-const CRITICAL_LONG = 'CRITICAL_LONG';
-const CRITICAL_SHORT = 'CRITICAL_SHORT';
-const HIGH_LONG = 'HIGH_LONG';
-const HIGH_SHORT = 'HIGH_SHORT';
-const MEDIUM_LONG = 'MEDIUM_LONG';
-const MEDIUM_SHORT = 'MEDIUM_SHORT';
-const LOW_LONG = 'LOW_LONG';
-const LOW_SHORT = 'LOW_SHORT';
+const CRITICAL = 'CRITICAL';
+const HIGH = 'HIGH';
+const MEDIUM = 'MEDIUM';
+const LOW = 'LOW';
const WINDOWS = [
- { name: CRITICAL_LONG, duration: '1h' },
- { name: CRITICAL_SHORT, duration: '5m' },
- { name: HIGH_LONG, duration: '6h' },
- { name: HIGH_SHORT, duration: '30m' },
- { name: MEDIUM_LONG, duration: '24h' },
- { name: MEDIUM_SHORT, duration: '120m' },
- { name: LOW_LONG, duration: '72h' },
- { name: LOW_SHORT, duration: '360m' },
+ { name: CRITICAL, duration: '1h' },
+ { name: HIGH, duration: '6h' },
+ { name: MEDIUM, duration: '24h' },
+ { name: LOW, duration: '72h' },
];
-function getSliAndBurnRate(name: string, burnRates: GetSLOBurnRatesResponse['burnRates']) {
- const data = burnRates.find((rate) => rate.name === name);
- if (!data) {
- return { burnRate: null, sli: null };
- }
- return { burnRate: data.burnRate, sli: data.sli };
-}
+const TIME_RANGE_OPTIONS = [
+ {
+ id: htmlIdGenerator()(),
+ label: i18n.translate('xpack.observability.slo.burnRates.fromRange.1hLabel', {
+ defaultMessage: '1h',
+ }),
+ windowName: CRITICAL,
+ threshold: 14.4,
+ duration: 1,
+ },
+ {
+ id: htmlIdGenerator()(),
+ label: i18n.translate('xpack.observability.slo.burnRates.fromRange.6hLabel', {
+ defaultMessage: '6h',
+ }),
+ windowName: HIGH,
+ threshold: 6,
+ duration: 6,
+ },
+ {
+ id: htmlIdGenerator()(),
+ label: i18n.translate('xpack.observability.slo.burnRates.fromRange.24hLabel', {
+ defaultMessage: '24h',
+ }),
+ windowName: MEDIUM,
+ threshold: 3,
+ duration: 24,
+ },
+ {
+ id: htmlIdGenerator()(),
+ label: i18n.translate('xpack.observability.slo.burnRates.fromRange.72hLabel', {
+ defaultMessage: '72h',
+ }),
+ windowName: LOW,
+ threshold: 1,
+ duration: 72,
+ },
+];
export function BurnRates({ slo, isAutoRefreshing }: Props) {
const { isLoading, data } = useFetchSloBurnRates({
@@ -60,118 +85,77 @@ export function BurnRates({ slo, isAutoRefreshing }: Props) {
windows: WINDOWS,
});
- const criticalWindowParams: BurnRateWindowParams = {
- title: i18n.translate('xpack.observability.slo.burnRate.criticalTitle', {
- defaultMessage: 'Critical burn rate',
- }),
- target: 14.4,
- longWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.criticalLongLabel', {
- defaultMessage: '1 hour',
- }),
- ...getSliAndBurnRate(CRITICAL_LONG, data?.burnRates ?? []),
- },
- shortWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.criticalShortLabel', {
- defaultMessage: '5 minute',
- }),
- ...getSliAndBurnRate(CRITICAL_SHORT, data?.burnRates ?? []),
- },
- };
-
- const highWindowParams: BurnRateWindowParams = {
- title: i18n.translate('xpack.observability.slo.burnRate.highTitle', {
- defaultMessage: 'High burn rate',
- }),
- target: 6,
- longWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.highLongLabel', {
- defaultMessage: '6 hour',
- }),
- ...getSliAndBurnRate(HIGH_LONG, data?.burnRates ?? []),
- },
- shortWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.highShortLabel', {
- defaultMessage: '30 minute',
- }),
- ...getSliAndBurnRate(HIGH_SHORT, data?.burnRates ?? []),
- },
+ const [timeRangeIdSelected, setTimeRangeIdSelected] = useState(TIME_RANGE_OPTIONS[0].id);
+ const [timeRange, setTimeRange] = useState(TIME_RANGE_OPTIONS[0]);
+ const onChange = (optionId: string) => {
+ setTimeRangeIdSelected(optionId);
};
- const mediumWindowParams: BurnRateWindowParams = {
- title: i18n.translate('xpack.observability.slo.burnRate.mediumTitle', {
- defaultMessage: 'Medium burn rate',
- }),
- target: 3,
- longWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.mediumLongLabel', {
- defaultMessage: '24 hours',
- }),
- ...getSliAndBurnRate(MEDIUM_LONG, data?.burnRates ?? []),
- },
- shortWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.mediumShortLabel', {
- defaultMessage: '2 hours',
- }),
- ...getSliAndBurnRate(MEDIUM_SHORT, data?.burnRates ?? []),
- },
- };
+ useEffect(() => {
+ const selected =
+ TIME_RANGE_OPTIONS.find((opt) => opt.id === timeRangeIdSelected) ?? TIME_RANGE_OPTIONS[0];
+ setTimeRange(selected);
+ }, [timeRangeIdSelected]);
- const lowWindowParams: BurnRateWindowParams = {
- title: i18n.translate('xpack.observability.slo.burnRate.lowTitle', {
- defaultMessage: 'Low burn rate',
- }),
- target: 1,
- longWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.lowLongLabel', {
- defaultMessage: '3 days',
- }),
- ...getSliAndBurnRate(LOW_LONG, data?.burnRates ?? []),
- },
- shortWindow: {
- label: i18n.translate('xpack.observability.slo.burnRate.lowShortLabel', {
- defaultMessage: '6 hours',
- }),
- ...getSliAndBurnRate(LOW_SHORT, data?.burnRates ?? []),
- },
- };
+ const fromRange = moment().subtract(timeRange.duration, 'hour').toDate();
+ const threshold = timeRange.threshold;
+ const burnRate = data?.burnRates.find((br) => br.name === timeRange.windowName)?.burnRate;
return (
-
-
-
-
- {i18n.translate('xpack.observability.slo.burnRate.title', {
- defaultMessage: 'Burn rate windows',
+
+
+
+
+
+
+ {i18n.translate('xpack.observability.slo.burnRate.title', {
+ defaultMessage: 'Burn rate',
+ })}{' '}
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+ options={TIME_RANGE_OPTIONS}
+ idSelected={timeRangeIdSelected}
+ onChange={(id) => onChange(id)}
+ buttonSize="compressed"
+ />
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
);
}
diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx
index f837627ed614f..d70a4cbbfcbe3 100644
--- a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx
@@ -13,19 +13,19 @@ import {
EuiTabbedContent,
EuiTabbedContentTab,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
import React, { Fragment, useState } from 'react';
-import { i18n } from '@kbn/i18n';
import { useLocation } from 'react-router-dom';
import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts';
-import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary';
+import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
+import { BurnRates } from './burn_rates';
import { ErrorBudgetChartPanel } from './error_budget_chart_panel';
import { Overview } from './overview/overview';
import { SliChartPanel } from './sli_chart_panel';
import { SloDetailsAlerts } from './slo_detail_alerts';
-import { BurnRates } from './burn_rates';
export interface Props {
slo: SLOWithSummaryResponse;
diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx
index c40b6f5265fec..5dad78763fe04 100644
--- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx
@@ -65,6 +65,9 @@ const mockKibana = () => {
useKibanaMock.mockReturnValue({
services: {
theme: {},
+ lens: {
+ EmbeddableComponent: () => mocked component
,
+ },
application: { navigateToUrl: mockNavigate },
charts: chartPluginMock.createStartContract(),
http: {
@@ -184,6 +187,7 @@ describe('SLO Details Page', () => {
expect(screen.queryByTestId('overview')).toBeTruthy();
expect(screen.queryByTestId('sliChartPanel')).toBeTruthy();
expect(screen.queryByTestId('errorBudgetChartPanel')).toBeTruthy();
+ expect(screen.queryByTestId('errorRateChart')).toBeTruthy();
expect(screen.queryAllByTestId('wideChartLoading').length).toBe(0);
});
diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts
index 77407c9bd25e8..8b7b3ce386c34 100644
--- a/x-pack/plugins/observability/public/plugin.ts
+++ b/x-pack/plugins/observability/public/plugin.ts
@@ -56,6 +56,7 @@ import {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
} from '@kbn/observability-ai-assistant-plugin/public';
+import { AiopsPluginStart } from '@kbn/aiops-plugin/public/types';
import { RulesLocatorDefinition } from './locators/rules';
import { RuleDetailsLocatorDefinition } from './locators/rule_details';
import { SloDetailsLocatorDefinition } from './locators/slo_details';
@@ -137,6 +138,7 @@ export interface ObservabilityPublicPluginsStart {
unifiedSearch: UnifiedSearchPublicPluginStart;
home?: HomePublicPluginStart;
cloud?: CloudStart;
+ aiops: AiopsPluginStart;
}
export type ObservabilityPublicStart = ReturnType;
diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json
index 73543b3a89453..35137bbddb002 100644
--- a/x-pack/plugins/observability/tsconfig.json
+++ b/x-pack/plugins/observability/tsconfig.json
@@ -85,6 +85,7 @@
"@kbn/deeplinks-analytics",
"@kbn/observability-ai-assistant-plugin",
"@kbn/osquery-plugin",
+ "@kbn/aiops-plugin",
"@kbn/content-management-plugin"
],
"exclude": [
diff --git a/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts b/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts
index f230a7097cf6d..cea6bd835b985 100644
--- a/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts
+++ b/x-pack/plugins/observability_shared/public/hooks/use_link_props.ts
@@ -19,6 +19,7 @@ export interface LinkDescriptor {
pathname?: string;
hash?: string;
search?: Search;
+ state?: unknown;
}
export interface LinkProps {
@@ -31,7 +32,7 @@ export interface Options {
}
export const useLinkProps = (
- { app, pathname, hash, search }: LinkDescriptor,
+ { app, pathname, hash, search, state }: LinkDescriptor,
options: Options = {}
): LinkProps => {
validateParams({ app, pathname, hash, search });
@@ -77,7 +78,7 @@ export const useLinkProps = (
const navigate = () => {
if (navigateToApp) {
const navigationPath = mergedHash ? `#${mergedHash}` : mergedPathname;
- navigateToApp(app, { path: navigationPath ? navigationPath : undefined });
+ navigateToApp(app, { path: navigationPath ? navigationPath : undefined, state });
}
};
@@ -94,7 +95,7 @@ export const useLinkProps = (
navigate();
}
};
- }, [navigateToApp, mergedHash, mergedPathname, app, prompt]);
+ }, [prompt, navigateToApp, mergedHash, mergedPathname, app, state]);
return {
href,
diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx
index 118c78e290759..3aa6ea6513484 100644
--- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx
@@ -16,9 +16,41 @@ import { SearchBar } from '@kbn/unified-search-plugin/public';
import type { QueryBarComponentProps } from '.';
import { QueryBar } from '.';
+import type { DataViewFieldMap } from '@kbn/data-views-plugin/common';
+import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub';
+import { fields } from '@kbn/data-views-plugin/common/mocks';
+import { useKibana } from '../../lib/kibana';
+
+const getMockIndexPattern = () => ({
+ ...createStubDataView({
+ spec: {
+ id: '1234',
+ title: 'logstash-*',
+ fields: ((): DataViewFieldMap => {
+ const fieldMap: DataViewFieldMap = Object.create(null);
+ for (const field of fields) {
+ fieldMap[field.name] = { ...field };
+ }
+ return fieldMap;
+ })(),
+ },
+ }),
+});
+
const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
+jest.mock('../../lib/kibana');
describe('QueryBar ', () => {
+ (useKibana as jest.Mock).mockReturnValue({
+ services: {
+ data: {
+ dataViews: {
+ create: jest.fn().mockResolvedValue(getMockIndexPattern()),
+ clearInstanceCache: jest.fn(),
+ },
+ },
+ },
+ });
const mockOnChangeQuery = jest.fn();
const mockOnSubmitQuery = jest.fn();
const mockOnSavedQuery = jest.fn();
@@ -52,10 +84,10 @@ describe('QueryBar ', () => {
mockOnSavedQuery.mockClear();
});
- test('check if we format the appropriate props to QueryBar', () => {
- const wrapper = mount(
-
- {
+ await act(async () => {
+ const wrapper = await getWrapper(
+ {
onSubmitQuery={mockOnSubmitQuery}
onSavedQuery={mockOnSavedQuery}
/>
-
- );
- const {
- customSubmitButton,
- timeHistory,
- onClearSavedQuery,
- onFiltersUpdated,
- onQueryChange,
- onQuerySubmit,
- onSaved,
- onSavedQueryUpdated,
- ...searchBarProps
- } = wrapper.find(SearchBar).props();
+ );
- expect(searchBarProps).toEqual({
- dataTestSubj: undefined,
- dateRangeFrom: 'now/d',
- dateRangeTo: 'now/d',
- displayStyle: undefined,
- filters: [],
- indexPatterns: [
- {
- fields: [
- {
- aggregatable: true,
- name: '@timestamp',
- searchable: true,
- type: 'date',
- },
- {
- aggregatable: true,
- name: '@version',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.ephemeral_id',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.hostname',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.id',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test1',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test2',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test3',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test4',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test5',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test6',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test7',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'agent.test8',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: true,
- name: 'host.name',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: false,
- name: 'nestedField.firstAttributes',
- searchable: true,
- type: 'string',
- },
- {
- aggregatable: false,
- name: 'nestedField.secondAttributes',
- searchable: true,
- type: 'string',
- },
- ],
- title: 'filebeat-*,auditbeat-*,packetbeat-*',
- },
- ],
- isLoading: false,
- isRefreshPaused: true,
- query: {
- language: 'kuery',
- query: 'here: query',
- },
- refreshInterval: undefined,
- savedQuery: undefined,
- showAutoRefreshOnly: false,
- showDatePicker: false,
- showFilterBar: true,
- showQueryInput: true,
- showSaveQuery: true,
- showSubmitButton: false,
+ await waitFor(() => {
+ wrapper.update();
+ const {
+ customSubmitButton,
+ timeHistory,
+ onClearSavedQuery,
+ onFiltersUpdated,
+ onQueryChange,
+ onQuerySubmit,
+ onSaved,
+ onSavedQueryUpdated,
+ ...searchBarProps
+ } = wrapper.find(SearchBar).props();
+ expect((searchBarProps?.indexPatterns ?? [{ id: 'unknown' }])[0].id).toEqual(
+ getMockIndexPattern().id
+ );
+ });
});
});
@@ -294,7 +208,6 @@ describe('QueryBar ', () => {
const onSubmitQueryRef = searchBarProps.onQuerySubmit;
const onSavedQueryRef = searchBarProps.onSavedQueryUpdated;
wrapper.setProps({ onSavedQuery: jest.fn() });
- wrapper.update();
expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated);
expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange);
diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx
index d86f3de10b549..aea4874f9a2ef 100644
--- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useMemo, useCallback } from 'react';
+import React, { memo, useMemo, useCallback, useState, useEffect } from 'react';
import deepEqual from 'fast-deep-equal';
import type { DataViewBase, Filter, Query, TimeRange } from '@kbn/es-query';
@@ -16,6 +16,8 @@ import type { SearchBarProps } from '@kbn/unified-search-plugin/public';
import { SearchBar } from '@kbn/unified-search-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
+import { useKibana } from '../../lib/kibana';
+
export interface QueryBarComponentProps {
dataTestSubj?: string;
dateRangeFrom?: string;
@@ -36,6 +38,9 @@ export interface QueryBarComponentProps {
isDisabled?: boolean;
}
+export const isDataView = (obj: unknown): obj is DataView =>
+ obj != null && typeof obj === 'object' && Object.hasOwn(obj, 'getName');
+
export const QueryBar = memo(
({
dateRangeFrom,
@@ -56,6 +61,8 @@ export const QueryBar = memo(
displayStyle,
isDisabled,
}) => {
+ const { data } = useKibana().services;
+ const [dataView, setDataView] = useState();
const onQuerySubmit = useCallback(
(payload: { dateRange: TimeRange; query?: Query }) => {
if (payload.query != null && !deepEqual(payload.query, filterQuery)) {
@@ -102,16 +109,32 @@ export const QueryBar = memo(
[filterManager]
);
- const indexPatterns = useMemo(() => [indexPattern], [indexPattern]);
- const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []);
+ useEffect(() => {
+ if (isDataView(indexPattern)) {
+ setDataView(indexPattern);
+ } else {
+ const createDataView = async () => {
+ const dv = await data.dataViews.create({ title: indexPattern.title });
+ setDataView(dv);
+ };
+ createDataView();
+ }
+ return () => {
+ if (dataView?.id) {
+ data.dataViews.clearInstanceCache(dataView?.id);
+ }
+ };
+ }, [data.dataViews, dataView?.id, indexPattern]);
+ const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []);
+ const arrDataView = useMemo(() => (dataView != null ? [dataView] : []), [dataView]);
return (
{
export const cli = () => {
run(
async () => {
+ const log = new ToolingLog({
+ level: 'info',
+ writeTo: process.stdout,
+ });
+
const { argv } = yargs(process.argv.slice(2))
- .coerce('spec', (arg) => (_.isArray(arg) ? [_.last(arg)] : [arg]))
+ .coerce('configFile', (arg) => (_.isArray(arg) ? _.last(arg) : arg))
+ .coerce('spec', (arg) => (_.isArray(arg) ? _.last(arg) : arg))
.coerce('env', (arg: string) =>
arg.split(',').reduce((acc, curr) => {
const [key, value] = curr.split('=');
@@ -77,22 +83,72 @@ export const cli = () => {
}, {} as Record)
);
+ log.info(`
+----------------------------------------------
+Script arguments:
+----------------------------------------------
+
+${JSON.stringify(argv, null, 2)}
+
+----------------------------------------------
+`);
+
const isOpen = argv._[0] === 'open';
- const cypressConfigFilePath = require.resolve(
- `../../${_.isArray(argv.configFile) ? _.last(argv.configFile) : argv.configFile}`
- ) as string;
+
+ const cypressConfigFilePath = require.resolve(`../../${argv.configFile}`) as string;
const cypressConfigFile = await import(cypressConfigFilePath);
+
+ log.info(`
+----------------------------------------------
+Cypress config for file: ${cypressConfigFilePath}:
+----------------------------------------------
+
+${JSON.stringify(cypressConfigFile, null, 2)}
+
+----------------------------------------------
+`);
+
+ const specConfig = cypressConfigFile.e2e.specPattern;
+ const specArg = argv.spec;
+ const specPattern = specArg ?? specConfig;
+
+ log.info('Config spec pattern:', specConfig);
+ log.info('Arguments spec pattern:', specArg);
+ log.info('Resulting spec pattern:', specPattern);
+
+ // The grep function will filter Cypress specs by tags: it will include and exclude
+ // spec files according to the tags configuration.
const grepSpecPattern = grep({
...cypressConfigFile,
- specPattern: argv.spec ?? cypressConfigFile.e2e.specPattern,
+ specPattern,
excludeSpecPattern: [],
}).specPattern;
- let files = retrieveIntegrations(
- _.isArray(grepSpecPattern)
- ? grepSpecPattern
- : globby.sync(argv.spec ?? cypressConfigFile.e2e.specPattern)
- );
+ log.info('Resolved spec files or pattern after grep:', grepSpecPattern);
+
+ const isGrepReturnedFilePaths = _.isArray(grepSpecPattern);
+ const isGrepReturnedSpecPattern = !isGrepReturnedFilePaths && grepSpecPattern === specPattern;
+
+ // IMPORTANT!
+ // When grep returns the same spec pattern as it gets in its arguments, we treat it as
+ // it couldn't find any concrete specs to execute (maybe because all of them are skipped).
+ // In this case, we do an early return - it's important to do that.
+ // If we don't return early, these specs will start executing, and Cypress will be skipping
+ // tests at runtime: those that should be excluded according to the tags passed in the config.
+ // This can take so much time that the job can fail by timeout in CI.
+ if (isGrepReturnedSpecPattern) {
+ log.info('No tests found - all tests could have been skipped via Cypress tags');
+ // eslint-disable-next-line no-process-exit
+ return process.exit(0);
+ }
+
+ const concreteFilePaths = isGrepReturnedFilePaths
+ ? grepSpecPattern // use the returned concrete file paths
+ : globby.sync(specPattern); // convert the glob pattern to concrete file paths
+
+ let files = retrieveIntegrations(concreteFilePaths);
+
+ log.info('Resolved spec files after retrieveIntegrations:', files);
if (argv.changedSpecsOnly) {
files = (findChangedFiles('main', false) as string[]).reduce((acc, itemPath) => {
@@ -108,11 +164,6 @@ export const cli = () => {
files = files.slice(0, 3);
}
- const log = new ToolingLog({
- level: 'info',
- writeTo: process.stdout,
- });
-
if (!files?.length) {
log.info('No tests found');
// eslint-disable-next-line no-process-exit
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index 8e517ada4ddca..c643f616e5eb3 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -4796,7 +4796,7 @@
"total": {
"type": "long",
"_meta": {
- "description": "Total number of shards for span and trasnaction indices"
+ "description": "Total number of shards for span and transaction indices"
}
}
}
@@ -4810,7 +4810,7 @@
"count": {
"type": "long",
"_meta": {
- "description": "Total number of transaction and span documents overall"
+ "description": "Total number of metric documents overall"
}
}
}
@@ -4820,7 +4820,7 @@
"size_in_bytes": {
"type": "long",
"_meta": {
- "description": "Size of the index in byte units overall."
+ "description": "Size of the metric indicess in byte units overall."
}
}
}
diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx
index 505009784d26e..2812475f7f87a 100644
--- a/x-pack/plugins/transform/public/app/app.tsx
+++ b/x-pack/plugins/transform/public/app/app.tsx
@@ -20,6 +20,7 @@ import { AppDependencies } from './app_dependencies';
import { CloneTransformSection } from './sections/clone_transform';
import { CreateTransformSection } from './sections/create_transform';
import { TransformManagementSection } from './sections/transform_management';
+import { ServerlessContextProvider } from './serverless_context';
export const App: FC<{ history: ScopedHistory }> = ({ history }) => (
@@ -37,7 +38,11 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => (
);
-export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => {
+export const renderApp = (
+ element: HTMLElement,
+ appDependencies: AppDependencies,
+ isServerless: boolean
+) => {
const I18nContext = appDependencies.i18n.Context;
const queryClient = new QueryClient({
@@ -55,7 +60,9 @@ export const renderApp = (element: HTMLElement, appDependencies: AppDependencies
-
+
+
+
diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts
index 2d3d7cfa9defd..60b31f9187fd6 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts
+++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts
@@ -18,7 +18,7 @@ import {
import { useAppDependencies } from '../app_dependencies';
-export const useGetTransformNodes = () => {
+export const useGetTransformNodes = ({ enabled } = { enabled: true }) => {
const { http } = useAppDependencies();
return useQuery(
@@ -36,6 +36,7 @@ export const useGetTransformNodes = () => {
},
{
refetchInterval: DEFAULT_REFRESH_INTERVAL_MS,
+ enabled,
}
);
};
diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts
index cb4032455adf2..86b2a297ed636 100644
--- a/x-pack/plugins/transform/public/app/mount_management_section.ts
+++ b/x-pack/plugins/transform/public/app/mount_management_section.ts
@@ -22,7 +22,8 @@ const localStorage = new Storage(window.localStorage);
export async function mountManagementSection(
coreSetup: CoreSetup,
- params: ManagementAppMountParams
+ params: ManagementAppMountParams,
+ isServerless: boolean
) {
const { element, setBreadcrumbs, history } = params;
const { http, getStartServices } = coreSetup;
@@ -92,7 +93,7 @@ export async function mountManagementSection(
contentManagement,
};
- const unmountAppCallback = renderApp(element, appDependencies);
+ const unmountAppCallback = renderApp(element, appDependencies, isServerless);
return () => {
docTitle.reset();
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx
index 849c651b37ea0..58f9cd2a455e2 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { type FC } from 'react';
+import React, { useMemo, type FC } from 'react';
import { Stat, StatsBarStat } from './stat';
@@ -18,10 +18,10 @@ export interface TransformStatsBarStats extends Stats {
batch: StatsBarStat;
continuous: StatsBarStat;
started: StatsBarStat;
+ nodes?: StatsBarStat;
}
type StatsBarStats = TransformStatsBarStats;
-type StatsKey = keyof StatsBarStats;
interface StatsBarProps {
stats: StatsBarStats;
@@ -29,7 +29,8 @@ interface StatsBarProps {
}
export const StatsBar: FC = ({ stats, dataTestSub }) => {
- const statsList = Object.keys(stats).map((k) => stats[k as StatsKey]);
+ const statsList = useMemo(() => Object.values(stats), [stats]);
+
return (
{statsList
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx
index d756c4a459e6a..f4766da492f67 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx
@@ -17,6 +17,7 @@ import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils';
import { stringHash } from '@kbn/ml-string-hash';
import { isDefined } from '@kbn/ml-is-defined';
+import { useIsServerless } from '../../../../serverless_context';
import { TransformHealthAlertRule } from '../../../../../../common/types/alerting';
import { TransformListRow } from '../../../../common';
@@ -46,6 +47,8 @@ interface Props {
type StateValues = Optional
;
export const ExpandedRow: FC = ({ item, onAlertEdit }) => {
+ const hideNodeInfo = useIsServerless();
+
const stateValues: StateValues = { ...item.stats };
delete stateValues.stats;
delete stateValues.checkpointing;
@@ -61,7 +64,7 @@ export const ExpandedRow: FC = ({ item, onAlertEdit }) => {
description: item.stats.state,
}
);
- if (item.stats.node !== undefined) {
+ if (!hideNodeInfo && item.stats.node !== undefined) {
stateItems.push({
title: 'node.name',
description: item.stats.node.name,
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx
index 2ec73e4485dd1..10c1a4f01dfb2 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx
@@ -19,6 +19,7 @@ import {
import { euiLightVars as theme } from '@kbn/ui-theme';
import { i18n } from '@kbn/i18n';
+import { useIsServerless } from '../../../../serverless_context';
import { DEFAULT_MAX_AUDIT_MESSAGE_SIZE, TIME_FORMAT } from '../../../../../../common/constants';
import { TransformMessage } from '../../../../../../common/types/messages';
@@ -35,6 +36,8 @@ interface Sorting {
}
export const ExpandedRowMessagesPane: FC = ({ transformId }) => {
+ const hideNodeInfo = useIsServerless();
+
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const [sorting, setSorting] = useState<{ sort: Sorting }>({
@@ -96,16 +99,20 @@ export const ExpandedRowMessagesPane: FC = ({ tran
render: (timestamp: number) => formatDate(timestamp, TIME_FORMAT),
sortable: true,
},
- {
- field: 'node_name',
- name: i18n.translate(
- 'xpack.transform.transformList.transformDetails.messagesPane.nodeLabel',
- {
- defaultMessage: 'Node',
- }
- ),
- sortable: true,
- },
+ ...(!hideNodeInfo
+ ? [
+ {
+ field: 'node_name',
+ name: i18n.translate(
+ 'xpack.transform.transformList.transformDetails.messagesPane.nodeLabel',
+ {
+ defaultMessage: 'Node',
+ }
+ ),
+ sortable: true,
+ },
+ ]
+ : []),
{
field: 'message',
name: i18n.translate(
@@ -114,7 +121,7 @@ export const ExpandedRowMessagesPane: FC = ({ tran
defaultMessage: 'Message',
}
),
- width: '50%',
+ width: !hideNodeInfo ? '50%' : '70%',
},
];
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx
index a041466eb5434..79f6321c2419f 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx
@@ -12,6 +12,7 @@ import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
+import { useIsServerless } from '../../../../serverless_context';
import { TRANSFORM_MODE, TRANSFORM_STATE } from '../../../../../../common/constants';
import { TransformListRow } from '../../../../common';
@@ -20,8 +21,12 @@ import { useDocumentationLinks, useRefreshTransformList } from '../../../../hook
import { StatsBar, TransformStatsBarStats } from '../stats_bar';
-function createTranformStats(transformNodes: number, transformsList: TransformListRow[]) {
- const transformStats = {
+function createTranformStats(
+ transformNodes: number,
+ transformsList: TransformListRow[],
+ hideNodeInfo: boolean
+): TransformStatsBarStats {
+ const transformStats: TransformStatsBarStats = {
total: {
label: i18n.translate('xpack.transform.statsBar.totalTransformsLabel', {
defaultMessage: 'Total transforms',
@@ -57,14 +62,17 @@ function createTranformStats(transformNodes: number, transformsList: TransformLi
value: 0,
show: true,
},
- nodes: {
+ };
+
+ if (!hideNodeInfo) {
+ transformStats.nodes = {
label: i18n.translate('xpack.transform.statsBar.transformNodesLabel', {
defaultMessage: 'Nodes',
}),
value: transformNodes,
show: true,
- },
- };
+ };
+ }
if (transformsList === undefined) {
return transformStats;
@@ -74,9 +82,15 @@ function createTranformStats(transformNodes: number, transformsList: TransformLi
let startedTransforms = 0;
transformsList.forEach((transform) => {
- if (transform.mode === TRANSFORM_MODE.CONTINUOUS) {
+ if (
+ transform.mode === TRANSFORM_MODE.CONTINUOUS &&
+ typeof transformStats.continuous.value === 'number'
+ ) {
transformStats.continuous.value++;
- } else if (transform.mode === TRANSFORM_MODE.BATCH) {
+ } else if (
+ transform.mode === TRANSFORM_MODE.BATCH &&
+ typeof transformStats.batch.value === 'number'
+ ) {
transformStats.batch.value++;
}
@@ -109,17 +123,19 @@ export const TransformStatsBar: FC = ({
transformNodes,
transformsList,
}) => {
+ const hideNodeInfo = useIsServerless();
const refreshTransformList = useRefreshTransformList();
const { esNodeRoles } = useDocumentationLinks();
const transformStats: TransformStatsBarStats = createTranformStats(
transformNodes,
- transformsList
+ transformsList,
+ hideNodeInfo
);
return (
<>
- {transformNodes === 0 && (
+ {!hideNodeInfo && transformNodes === 0 && (
<>
{
const { esTransform } = useDocumentationLinks();
+ const hideNodeInfo = useIsServerless();
const deleteTransforms = useDeleteTransforms();
@@ -78,7 +80,7 @@ export const TransformManagement: FC = () => {
isInitialLoading: transformNodesInitialLoading,
error: transformNodesErrorMessage,
data: transformNodesData = 0,
- } = useGetTransformNodes();
+ } = useGetTransformNodes({ enabled: true });
const transformNodes = transformNodesErrorMessage === null ? transformNodesData : 0;
const {
@@ -86,7 +88,9 @@ export const TransformManagement: FC = () => {
isLoading: transformsLoading,
error: transformsErrorMessage,
data: { transforms, transformIdsWithoutConfig },
- } = useGetTransforms({ enabled: !transformNodesInitialLoading && transformNodes > 0 });
+ } = useGetTransforms({
+ enabled: !transformNodesInitialLoading && (transformNodes > 0 || hideNodeInfo),
+ });
const isInitialLoading = transformNodesInitialLoading || transformsInitialLoading;
@@ -193,7 +197,7 @@ export const TransformManagement: FC = () => {
<>
{unauthorizedTransformsWarning}
- {transformNodesErrorMessage !== null && (
+ {!hideNodeInfo && transformNodesErrorMessage !== null && (
= (props) => {
+ const { children, isServerless } = props;
+ return (
+ {children}
+ );
+};
+
+export function useIsServerless() {
+ const context = useContext(ServerlessContext);
+ return useMemo(() => {
+ return context.isServerless;
+ }, [context]);
+}
diff --git a/x-pack/plugins/transform/public/index.ts b/x-pack/plugins/transform/public/index.ts
index ebe43aea75440..0b185da7f6900 100644
--- a/x-pack/plugins/transform/public/index.ts
+++ b/x-pack/plugins/transform/public/index.ts
@@ -6,11 +6,12 @@
*/
import './app/index.scss';
+import type { PluginInitializerContext } from '@kbn/core-plugins-server';
import { TransformUiPlugin } from './plugin';
/** @public */
-export const plugin = () => {
- return new TransformUiPlugin();
+export const plugin = (ctx: PluginInitializerContext) => {
+ return new TransformUiPlugin(ctx);
};
export { getTransformHealthRuleType } from './alerting';
diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts
index 789d76901aeaa..03b99ab85ad27 100644
--- a/x-pack/plugins/transform/public/plugin.ts
+++ b/x-pack/plugins/transform/public/plugin.ts
@@ -18,11 +18,12 @@ import type { SpacesApi } from '@kbn/spaces-plugin/public';
import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public';
import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
-import { ChartsPluginStart } from '@kbn/charts-plugin/public';
-import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
-import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public/plugin';
-import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
-import { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
+import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
+import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
+import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public/plugin';
+import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
+import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
+import type { PluginInitializerContext } from '@kbn/core/public';
import { registerFeature } from './register_feature';
import { getTransformHealthRuleType } from './alerting';
@@ -45,6 +46,11 @@ export interface PluginsDependencies {
}
export class TransformUiPlugin {
+ private isServerless: boolean = false;
+ constructor(initializerContext: PluginInitializerContext) {
+ this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless';
+ }
+
public setup(coreSetup: CoreSetup, pluginsSetup: PluginsDependencies): void {
const { management, home, triggersActionsUi } = pluginsSetup;
@@ -58,7 +64,7 @@ export class TransformUiPlugin {
order: 5,
mount: async (params) => {
const { mountManagementSection } = await import('./app/mount_management_section');
- return mountManagementSection(coreSetup, params);
+ return mountManagementSection(coreSetup, params, this.isServerless);
},
});
registerFeature(home);
diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json
index 2d0cdf45c554d..52e0747cc1fb9 100644
--- a/x-pack/plugins/transform/tsconfig.json
+++ b/x-pack/plugins/transform/tsconfig.json
@@ -68,7 +68,8 @@
"@kbn/unified-field-list",
"@kbn/ebt-tools",
"@kbn/content-management-plugin",
- "@kbn/react-kibana-mount"
+ "@kbn/react-kibana-mount",
+ "@kbn/core-plugins-server"
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 29fd5965bf442..5dd6f2a8a1513 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -16564,8 +16564,6 @@
"xpack.fleet.policyDetailsPackagePolicies.createFirstTitle": "Ajouter votre première intégration",
"xpack.fleet.policyForm.deletePolicyActionText": "Supprimer la stratégie",
"xpack.fleet.policyForm.deletePolicyActionText.disabled": "La politique d'agent avec les politiques de package géré ne peut pas être supprimée.",
- "xpack.fleet.policyForm.deletePolicyGroupDescription": "Les données existantes ne sont pas supprimées.",
- "xpack.fleet.policyForm.deletePolicyGroupTitle": "Supprimer la stratégie",
"xpack.fleet.policyForm.generalSettingsGroupDescription": "Attribuez un nom et ajoutez une description à votre stratégie d'agent.",
"xpack.fleet.policyForm.generalSettingsGroupTitle": "Paramètres généraux",
"xpack.fleet.renameAgentTags.errorNotificationTitle": "La balise n’a pas pu être renommée",
@@ -18663,7 +18661,6 @@
"xpack.infra.assetDetailsEmbeddable.displayName": "Détails de ressource",
"xpack.infra.assetDetailsEmbeddable.title": "Détails de ressource",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.cpuUsage.title": "Utilisation CPU",
- "xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title": "Utilisation de l’espace disque",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.memoryUsage.title": "Utilisation mémoire",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.normalizedLoad1m.title": "Charge normalisée",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average": "Moyenne",
@@ -18770,7 +18767,6 @@
"xpack.infra.hostsViewPage.metrics.tooltip.tx": "Nombre d'octets envoyés par seconde sur les interfaces publiques des hôtes.",
"xpack.infra.hostsViewPage.table.addFilter": "Ajouter un filtre",
"xpack.infra.hostsViewPage.table.cpuUsageColumnHeader": "Utilisation CPU (moy.)",
- "xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader": "Utilisation de l’espace disque (moy.)",
"xpack.infra.hostsViewPage.table.memoryFreeColumnHeader": "Sans mémoire (moy.)",
"xpack.infra.hostsViewPage.table.memoryUsageColumnHeader": "Utilisation de la mémoire (moy.)",
"xpack.infra.hostsViewPage.table.nameColumnHeader": "Nom",
@@ -18796,7 +18792,6 @@
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskIOWrite": "Entrées et sorties par seconde en écriture sur le disque",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskReadThroughput": "Rendement de lecture du disque",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceAvailable": "Espace disque disponible",
- "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed": "Utilisation de l’espace disque",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskWriteThroughput": "Rendement d’écriture du disque",
"xpack.infra.hostsViewPage.tabs.metricsCharts.memoryFree": "Sans mémoire",
"xpack.infra.hostsViewPage.tabs.metricsCharts.memoryUsage": "Utilisation mémoire",
@@ -27127,7 +27122,6 @@
"xpack.observability.inspector.stats.queryTimeValue": "{queryTime} ms",
"xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {détecté} Inventory {seuil dépassé} other {dépassé}}",
"xpack.observability.slo.alerting.burnRate.reason": "{actionGroupName} : Le taux d'avancement pour le (les) dernier(s) {longWindowDuration} est de {longWindowBurnRate} et pour le (les) dernier(s) {shortWindowDuration} est de {shortWindowBurnRate}. Alerter si supérieur à {burnRateThreshold} pour les deux fenêtres",
- "xpack.observability.slo.burnRateWindow.thresholdTip": "Le seuil est {target}x",
"xpack.observability.slo.clone.errorNotification": "Échec du clonage de {name}",
"xpack.observability.slo.clone.successNotification": "{name} créé avec succès",
"xpack.observability.slo.create.errorNotification": "Un problème est survenu lors de la création de {name}",
@@ -27412,18 +27406,6 @@
"xpack.observability.slo.alerting.windowDescription": "Durée de fenêtre avec la valeur du taux d'avancement associée.",
"xpack.observability.slo.budgetingMethod.occurrences": "Occurrences",
"xpack.observability.slo.budgetingMethod.timeslices": "Intervalles de temps",
- "xpack.observability.slo.burnRate.criticalLongLabel": "1 heure",
- "xpack.observability.slo.burnRate.criticalShortLabel": "5 minutes",
- "xpack.observability.slo.burnRate.criticalTitle": "Taux d’avancement critique",
- "xpack.observability.slo.burnRate.highLongLabel": "6 heures",
- "xpack.observability.slo.burnRate.highShortLabel": "30 minutes",
- "xpack.observability.slo.burnRate.highTitle": "Taux d’avancement élevé",
- "xpack.observability.slo.burnRate.lowLongLabel": "3 jours",
- "xpack.observability.slo.burnRate.lowShortLabel": "6 heures",
- "xpack.observability.slo.burnRate.lowTitle": "Taux d'avancement bas",
- "xpack.observability.slo.burnRate.mediumLongLabel": "24 heures",
- "xpack.observability.slo.burnRate.mediumShortLabel": "2 heures",
- "xpack.observability.slo.burnRate.mediumTitle": "Taux d’avancement moyen",
"xpack.observability.slo.burnRate.technicalPreviewBadgeDescription": "Cette fonctionnalité est en préversion technique et est susceptible d’être changée, ou elle peut-être supprimée dans les versions futures. La conception et le code sont moins matures que les fonctionnalités officielles en disponibilité générale et sont fournis tels quels sans aucune garantie. Les fonctionnalités de la version d’évaluation technique ne sont pas soumises à l'accord de niveau de service des fonctionnalités officielles en disponibilité générale.",
"xpack.observability.slo.burnRate.technicalPreviewBadgeTitle": "Version d'évaluation technique",
"xpack.observability.slo.burnRate.title": "Fenêtres du taux d’avancement",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 7c8ed5b0a5813..55a751c23164f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -16578,8 +16578,6 @@
"xpack.fleet.policyDetailsPackagePolicies.createFirstTitle": "最初の統合を追加",
"xpack.fleet.policyForm.deletePolicyActionText": "ポリシーを削除",
"xpack.fleet.policyForm.deletePolicyActionText.disabled": "管理されたパッケージポリシーのエージェントポリシーは削除できません。",
- "xpack.fleet.policyForm.deletePolicyGroupDescription": "既存のデータは削除されません。",
- "xpack.fleet.policyForm.deletePolicyGroupTitle": "ポリシーを削除",
"xpack.fleet.policyForm.generalSettingsGroupDescription": "エージェントポリシーの名前と説明を選択してください。",
"xpack.fleet.policyForm.generalSettingsGroupTitle": "一般設定",
"xpack.fleet.renameAgentTags.errorNotificationTitle": "タグ名の変更が失敗しました",
@@ -18677,7 +18675,6 @@
"xpack.infra.assetDetailsEmbeddable.displayName": "アセット詳細",
"xpack.infra.assetDetailsEmbeddable.title": "アセット詳細",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.cpuUsage.title": "CPU使用状況",
- "xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title": "ディスク容量使用状況",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.memoryUsage.title": "メモリー使用状況",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.normalizedLoad1m.title": "正規化された負荷",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average": "平均",
@@ -18784,7 +18781,6 @@
"xpack.infra.hostsViewPage.metrics.tooltip.tx": "ホストのパブリックインターフェースで1秒間に送信したバイト数。",
"xpack.infra.hostsViewPage.table.addFilter": "フィルターを追加します",
"xpack.infra.hostsViewPage.table.cpuUsageColumnHeader": "CPU使用状況(平均)",
- "xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader": "ディスク容量使用状況(平均)",
"xpack.infra.hostsViewPage.table.memoryFreeColumnHeader": "空きメモリー(平均)",
"xpack.infra.hostsViewPage.table.memoryUsageColumnHeader": "メモリー使用状況(平均)",
"xpack.infra.hostsViewPage.table.nameColumnHeader": "名前",
@@ -18810,7 +18806,6 @@
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskIOWrite": "ディスク書き込みIOPS",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskReadThroughput": "ディスク読み取りスループット",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceAvailable": "空きディスク容量",
- "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed": "ディスク容量使用状況",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskWriteThroughput": "ディスク書き込みスループット",
"xpack.infra.hostsViewPage.tabs.metricsCharts.memoryFree": "空きメモリー",
"xpack.infra.hostsViewPage.tabs.metricsCharts.memoryUsage": "メモリー使用状況",
@@ -27127,7 +27122,6 @@
"xpack.observability.inspector.stats.queryTimeValue": "{queryTime}ms",
"xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {が検出されました} Inventory {しきい値を超えました} other {を超えました}}",
"xpack.observability.slo.alerting.burnRate.reason": "{actionGroupName}:過去{longWindowDuration}のバーンレートは{longWindowBurnRate}で、過去{shortWindowDuration}のバーンレートは{shortWindowBurnRate}です。両期間とも{burnRateThreshold}を超えたらアラート",
- "xpack.observability.slo.burnRateWindow.thresholdTip": "しきい値は{target}xです",
"xpack.observability.slo.clone.errorNotification": "{name}を複製できませんでした",
"xpack.observability.slo.clone.successNotification": "{name}の作成が正常に完了しました",
"xpack.observability.slo.create.errorNotification": "{name}の作成中に問題が発生しました",
@@ -27412,18 +27406,6 @@
"xpack.observability.slo.alerting.windowDescription": "関連付けられたバーンレート値の期間。",
"xpack.observability.slo.budgetingMethod.occurrences": "オカレンス",
"xpack.observability.slo.budgetingMethod.timeslices": "タイムスライス",
- "xpack.observability.slo.burnRate.criticalLongLabel": "1時間",
- "xpack.observability.slo.burnRate.criticalShortLabel": "5分",
- "xpack.observability.slo.burnRate.criticalTitle": "重大バーンレート",
- "xpack.observability.slo.burnRate.highLongLabel": "6時間",
- "xpack.observability.slo.burnRate.highShortLabel": "30分",
- "xpack.observability.slo.burnRate.highTitle": "高バーンレート",
- "xpack.observability.slo.burnRate.lowLongLabel": "3日",
- "xpack.observability.slo.burnRate.lowShortLabel": "6時間",
- "xpack.observability.slo.burnRate.lowTitle": "低バーンレート",
- "xpack.observability.slo.burnRate.mediumLongLabel": "24時間",
- "xpack.observability.slo.burnRate.mediumShortLabel": "2時間",
- "xpack.observability.slo.burnRate.mediumTitle": "中バーンレート",
"xpack.observability.slo.burnRate.technicalPreviewBadgeDescription": "この機能はテクニカルプレビュー中であり、将来のバージョンで変更または削除される可能性があります。デザインとコードは正式に一般公開された機能より完成度が低く、現状のまま保証なしで提供されています。テクニカルプレビュー機能は、正式に一般公開された機能に適用されるサポートサービスレベル契約の対象外です。",
"xpack.observability.slo.burnRate.technicalPreviewBadgeTitle": "テクニカルプレビュー",
"xpack.observability.slo.burnRate.title": "バーンレート時間枠",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index b4899f5bd0b4c..cf393f0a4211c 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -16578,8 +16578,6 @@
"xpack.fleet.policyDetailsPackagePolicies.createFirstTitle": "添加您的首个集成",
"xpack.fleet.policyForm.deletePolicyActionText": "删除策略",
"xpack.fleet.policyForm.deletePolicyActionText.disabled": "无法删除包含托管软件包策略的代理策略。",
- "xpack.fleet.policyForm.deletePolicyGroupDescription": "现有数据将不会删除。",
- "xpack.fleet.policyForm.deletePolicyGroupTitle": "删除策略",
"xpack.fleet.policyForm.generalSettingsGroupDescription": "为您的代理策略选择名称和描述。",
"xpack.fleet.policyForm.generalSettingsGroupTitle": "常规设置",
"xpack.fleet.renameAgentTags.errorNotificationTitle": "标签重命名失败",
@@ -18677,7 +18675,6 @@
"xpack.infra.assetDetailsEmbeddable.displayName": "资产详情",
"xpack.infra.assetDetailsEmbeddable.title": "资产详情",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.cpuUsage.title": "CPU 使用率",
- "xpack.infra.assetDetailsEmbeddable.overview.kpi.diskSpaceUsage.title": "磁盘空间使用率",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.memoryUsage.title": "内存利用率",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.normalizedLoad1m.title": "标准化负载",
"xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average": "平均值",
@@ -18784,7 +18781,6 @@
"xpack.infra.hostsViewPage.metrics.tooltip.tx": "主机的公共接口上每秒发送的字节数。",
"xpack.infra.hostsViewPage.table.addFilter": "添加筛选",
"xpack.infra.hostsViewPage.table.cpuUsageColumnHeader": "CPU 使用率(平均值)",
- "xpack.infra.hostsViewPage.table.diskSpaceUsageColumnHeader": "磁盘空间使用率(平均值)",
"xpack.infra.hostsViewPage.table.memoryFreeColumnHeader": "可用内存(平均值)",
"xpack.infra.hostsViewPage.table.memoryUsageColumnHeader": "内存使用率(平均值)",
"xpack.infra.hostsViewPage.table.nameColumnHeader": "名称",
@@ -18810,7 +18806,6 @@
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskIOWrite": "磁盘写入 IOPS",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskReadThroughput": "磁盘读取吞吐量",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceAvailable": "可用磁盘空间",
- "xpack.infra.hostsViewPage.tabs.metricsCharts.diskSpaceUsed": "磁盘空间使用率",
"xpack.infra.hostsViewPage.tabs.metricsCharts.diskWriteThroughput": "磁盘写入吞吐量",
"xpack.infra.hostsViewPage.tabs.metricsCharts.memoryFree": "可用内存",
"xpack.infra.hostsViewPage.tabs.metricsCharts.memoryUsage": "内存利用率",
@@ -27125,7 +27120,6 @@
"xpack.observability.inspector.stats.queryTimeValue": "{queryTime}ms",
"xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {已检测到} Inventory {超出阈值} other {超出}}",
"xpack.observability.slo.alerting.burnRate.reason": "{actionGroupName}:过去 {longWindowDuration} 的消耗速度为 {longWindowBurnRate} 且过去 {shortWindowDuration} 为 {shortWindowBurnRate}。两个窗口超出 {burnRateThreshold} 时告警",
- "xpack.observability.slo.burnRateWindow.thresholdTip": "阈值为 {target}x",
"xpack.observability.slo.clone.errorNotification": "无法克隆 {name}",
"xpack.observability.slo.clone.successNotification": "已成功创建 {name}",
"xpack.observability.slo.create.errorNotification": "创建 {name} 时出现问题",
@@ -27410,18 +27404,6 @@
"xpack.observability.slo.alerting.windowDescription": "带有关联的消耗速度值的窗口持续时间。",
"xpack.observability.slo.budgetingMethod.occurrences": "发生次数",
"xpack.observability.slo.budgetingMethod.timeslices": "时间片",
- "xpack.observability.slo.burnRate.criticalLongLabel": "1 小时",
- "xpack.observability.slo.burnRate.criticalShortLabel": "5 分钟",
- "xpack.observability.slo.burnRate.criticalTitle": "临界消耗速度",
- "xpack.observability.slo.burnRate.highLongLabel": "6 小时",
- "xpack.observability.slo.burnRate.highShortLabel": "30 分钟",
- "xpack.observability.slo.burnRate.highTitle": "高消耗速度",
- "xpack.observability.slo.burnRate.lowLongLabel": "3 天",
- "xpack.observability.slo.burnRate.lowShortLabel": "6 小时",
- "xpack.observability.slo.burnRate.lowTitle": "低消耗速度",
- "xpack.observability.slo.burnRate.mediumLongLabel": "24 小时",
- "xpack.observability.slo.burnRate.mediumShortLabel": "2 小时",
- "xpack.observability.slo.burnRate.mediumTitle": "中等消耗速度",
"xpack.observability.slo.burnRate.technicalPreviewBadgeDescription": "此功能处于技术预览状态,可能会有所更改,或在未来版本中移除。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。技术预览功能不受正式发行版功能的支持服务水平协议约束。",
"xpack.observability.slo.burnRate.technicalPreviewBadgeTitle": "技术预览",
"xpack.observability.slo.burnRate.title": "消耗速度窗口",
diff --git a/x-pack/plugins/ux/kibana.jsonc b/x-pack/plugins/ux/kibana.jsonc
index 26a2ab78a926a..22d912cbe4ab3 100644
--- a/x-pack/plugins/ux/kibana.jsonc
+++ b/x-pack/plugins/ux/kibana.jsonc
@@ -17,7 +17,6 @@
"observabilityShared",
"observabilityAIAssistant",
"embeddable",
- "infra",
"inspector",
"apm"
],
diff --git a/x-pack/test/accessibility/apps/security_solution.ts b/x-pack/test/accessibility/apps/security_solution.ts
index ba7d22fd2d39d..cda47540f5d0f 100644
--- a/x-pack/test/accessibility/apps/security_solution.ts
+++ b/x-pack/test/accessibility/apps/security_solution.ts
@@ -14,7 +14,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const toasts = getService('toasts');
const testSubjects = getService('testSubjects');
- describe('Security Solution Accessibility', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/166102
+ // Failing: See https://github.com/elastic/kibana/issues/166105
+ describe.skip('Security Solution Accessibility', () => {
before(async () => {
await security.testUser.setRoles(['superuser'], { skipBrowserRefresh: true });
await common.navigateToApp('security');
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts
index 62d45f50a07c9..3bff92d2470c9 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts
@@ -298,6 +298,63 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F
expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(true);
});
+ it('Should not fail when an alert is flapping and recovered for a rule with notify_when: onThrottleInterval', async () => {
+ await supertest
+ .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
+ .set('kbn-xsrf', 'foo')
+ .auth('superuser', 'superuser')
+ .send({
+ enabled: true,
+ look_back_window: 5,
+ status_change_threshold: 3,
+ })
+ .expect(200);
+
+ const pattern = {
+ alertA: [true, false, true, false, false, false, false, false, false],
+ };
+ const ruleParameters = { pattern };
+ const createdRule = await supertestWithoutAuth
+ .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
+ .set('kbn-xsrf', 'foo')
+ .send(
+ // notify_when is not RuleNotifyWhen.CHANGE, so it's not added to activeCurrent
+ getTestRuleData({
+ rule_type_id: 'test.patternFiringAad',
+ // set the schedule long so we can use "runSoon" to specify rule runs
+ schedule: { interval: '1d' },
+ throttle: null,
+ params: ruleParameters,
+ actions: [],
+ })
+ );
+
+ expect(createdRule.status).to.eql(200);
+ const ruleId = createdRule.body.id;
+ objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting');
+
+ // Wait for the rule to run once
+ let run = 1;
+ await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 1 }]]));
+ // Run the rule 10 more times
+ for (let i = 0; i < 5; i++) {
+ const response = await supertestWithoutAuth
+ .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`)
+ .set('kbn-xsrf', 'foo');
+ expect(response.status).to.eql(204);
+ await waitForEventLogDocs(ruleId, new Map([['execute', { equal: ++run }]]));
+ }
+
+ const alertDocs = await queryForAlertDocs();
+ const state = await getRuleState(ruleId);
+
+ expect(alertDocs.length).to.equal(2);
+
+ // Alert is recovered and flapping
+ expect(alertDocs[0]._source!.kibana.alert.flapping).to.equal(true);
+ expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(true);
+ });
+
it('should set flapping and flapping_history for flapping alerts over a period of time longer than the lookback', async () => {
await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
diff --git a/x-pack/test/functional/apps/lens/group4/tsdb.ts b/x-pack/test/functional/apps/lens/group4/tsdb.ts
index edbef46dc1f08..3200c7a073dc4 100644
--- a/x-pack/test/functional/apps/lens/group4/tsdb.ts
+++ b/x-pack/test/functional/apps/lens/group4/tsdb.ts
@@ -384,8 +384,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
});
- // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/163971
- describe.skip('for rolled up metric (downsampled)', () => {
+ describe('for rolled up metric (downsampled)', () => {
it('defaults to average for rolled up metric', async () => {
await PageObjects.lens.switchDataPanelIndexPattern(downsampleDataView.dataView);
await PageObjects.lens.removeLayer();
@@ -622,21 +621,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
{ index: 'regular_index', create: true, removeTSDBFields: true },
],
},
- // {
- // name: 'Dataview with an additional downsampled TSDB stream',
- // indexes: [
- // { index: initialIndex },
- // { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true },
- // ],
- // },
- // {
- // name: 'Dataview with additional regular index and a downsampled TSDB stream',
- // indexes: [
- // { index: initialIndex },
- // { index: 'regular_index', create: true, removeTSDBFields: true },
- // { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true },
- // ],
- // },
+ {
+ name: 'Dataview with an additional downsampled TSDB stream',
+ indexes: [
+ { index: initialIndex },
+ { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true },
+ ],
+ },
+ {
+ name: 'Dataview with additional regular index and a downsampled TSDB stream',
+ indexes: [
+ { index: initialIndex },
+ { index: 'regular_index', create: true, removeTSDBFields: true },
+ { index: 'tsdb_index_2', create: true, tsdb: true, downsample: true },
+ ],
+ },
{
name: 'Dataview with an additional TSDB stream',
indexes: [{ index: initialIndex }, { index: 'tsdb_index_2', create: true, tsdb: true }],
diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts
index efe2da04fea57..a48075dc0d2fe 100644
--- a/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/cases/group2/list_view.ts
@@ -96,7 +96,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
});
- describe('severity', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/166123
+ describe.skip('severity', () => {
before(async () => {
await cases.api.createNthRandomCases(2);
await header.waitUntilLoadingHasFinished();
@@ -502,7 +503,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
});
- describe('severity filtering', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/166127
+ describe.skip('severity filtering', () => {
before(async () => {
await cases.navigation.navigateToApp();
await cases.api.createCase({ severity: CaseSeverity.LOW });
diff --git a/x-pack/test/monitoring_api_integration/apis/apm/index.ts b/x-pack/test/monitoring_api_integration/apis/apm/index.ts
index dbbae596a7b59..0171da7e6b83a 100644
--- a/x-pack/test/monitoring_api_integration/apis/apm/index.ts
+++ b/x-pack/test/monitoring_api_integration/apis/apm/index.ts
@@ -6,9 +6,12 @@
*/
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { installPackage } from '../../packages';
-export default function ({ loadTestFile }: FtrProviderContext) {
+export default function ({ loadTestFile, getService }: FtrProviderContext) {
describe('APM', () => {
+ before(() => installPackage(getService('supertest'), 'beat'));
+
loadTestFile(require.resolve('./overview'));
loadTestFile(require.resolve('./instances'));
});
diff --git a/x-pack/test/monitoring_api_integration/apis/beats/index.ts b/x-pack/test/monitoring_api_integration/apis/beats/index.ts
index 01d2bc6a3c3d6..79d051835ec3a 100644
--- a/x-pack/test/monitoring_api_integration/apis/beats/index.ts
+++ b/x-pack/test/monitoring_api_integration/apis/beats/index.ts
@@ -6,9 +6,12 @@
*/
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { installPackage } from '../../packages';
-export default function ({ loadTestFile }: FtrProviderContext) {
+export default function ({ loadTestFile, getService }: FtrProviderContext) {
describe('Beats', () => {
+ before(() => installPackage(getService('supertest'), 'beat'));
+
loadTestFile(require.resolve('./overview'));
loadTestFile(require.resolve('./beats'));
loadTestFile(require.resolve('./beat'));
diff --git a/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts b/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts
index 8b43525fb03e2..cfcb7bf5570aa 100644
--- a/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts
+++ b/x-pack/test/monitoring_api_integration/apis/elasticsearch/index.ts
@@ -6,9 +6,12 @@
*/
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { installPackage } from '../../packages';
-export default function ({ loadTestFile }: FtrProviderContext) {
+export default function ({ loadTestFile, getService }: FtrProviderContext) {
describe('Elasticsearch', () => {
+ before(() => installPackage(getService('supertest'), 'elasticsearch'));
+
loadTestFile(require.resolve('./ccr'));
loadTestFile(require.resolve('./indices'));
loadTestFile(require.resolve('./ml_jobs'));
diff --git a/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts b/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts
index 5942fde0fe95a..a7195e283232a 100644
--- a/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts
+++ b/x-pack/test/monitoring_api_integration/apis/enterprisesearch/index.ts
@@ -6,9 +6,12 @@
*/
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { installPackage } from '../../packages';
-export default function ({ loadTestFile }: FtrProviderContext) {
+export default function ({ loadTestFile, getService }: FtrProviderContext) {
describe('Enterprisesearch', () => {
+ before(() => installPackage(getService('supertest'), 'enterprisesearch'));
+
loadTestFile(require.resolve('./overview'));
});
}
diff --git a/x-pack/test/monitoring_api_integration/apis/kibana/index.ts b/x-pack/test/monitoring_api_integration/apis/kibana/index.ts
index 24c5e6865021a..4409c5a871397 100644
--- a/x-pack/test/monitoring_api_integration/apis/kibana/index.ts
+++ b/x-pack/test/monitoring_api_integration/apis/kibana/index.ts
@@ -6,9 +6,12 @@
*/
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { installPackage } from '../../packages';
-export default function ({ loadTestFile }: FtrProviderContext) {
+export default function ({ loadTestFile, getService }: FtrProviderContext) {
describe('Kibana', () => {
+ before(() => installPackage(getService('supertest'), 'kibana'));
+
loadTestFile(require.resolve('./overview'));
loadTestFile(require.resolve('./instances'));
});
diff --git a/x-pack/test/monitoring_api_integration/apis/logstash/index.ts b/x-pack/test/monitoring_api_integration/apis/logstash/index.ts
index 88a5980273ef5..bcde9e2cc5f31 100644
--- a/x-pack/test/monitoring_api_integration/apis/logstash/index.ts
+++ b/x-pack/test/monitoring_api_integration/apis/logstash/index.ts
@@ -6,9 +6,12 @@
*/
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { installPackage } from '../../packages';
-export default function ({ loadTestFile }: FtrProviderContext) {
+export default function ({ loadTestFile, getService }: FtrProviderContext) {
describe('Logstash', () => {
+ before(() => installPackage(getService('supertest'), 'logstash'));
+
loadTestFile(require.resolve('./overview'));
loadTestFile(require.resolve('./nodes'));
loadTestFile(require.resolve('./pipelines'));
diff --git a/x-pack/test/monitoring_api_integration/config.ts b/x-pack/test/monitoring_api_integration/config.ts
index cd016afb6dcfd..786fde0bb1d30 100644
--- a/x-pack/test/monitoring_api_integration/config.ts
+++ b/x-pack/test/monitoring_api_integration/config.ts
@@ -7,8 +7,6 @@
import { FtrConfigProviderContext } from '@kbn/test';
-import { bundledPackagesLocation, getPackagesArgs } from './packages';
-
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
@@ -22,11 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
esTestCluster: xPackAPITestsConfig.get('esTestCluster'),
kbnTestServer: {
...xPackAPITestsConfig.get('kbnTestServer'),
- serverArgs: [
- ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
- `--xpack.fleet.developer.bundledPackageLocation=${bundledPackagesLocation}`,
- ...getPackagesArgs(),
- ],
+ serverArgs: [...xPackAPITestsConfig.get('kbnTestServer.serverArgs')],
},
};
}
diff --git a/x-pack/test/monitoring_api_integration/packages.ts b/x-pack/test/monitoring_api_integration/packages.ts
index aaa11d8d0fe9e..284bba089a55c 100644
--- a/x-pack/test/monitoring_api_integration/packages.ts
+++ b/x-pack/test/monitoring_api_integration/packages.ts
@@ -6,6 +6,10 @@
*/
import path from 'path';
+import { createReadStream } from 'fs';
+import type SuperTest from 'supertest';
+
+type SupportedPackage = 'beat' | 'elasticsearch' | 'enterprisesearch' | 'logstash' | 'kibana';
const PACKAGES = [
{ name: 'beat', version: '0.1.3' },
@@ -25,3 +29,26 @@ export const getPackagesArgs = (): string[] => {
};
export const bundledPackagesLocation = path.join(path.dirname(__filename), '/fixtures/packages');
+
+export function installPackage(
+ supertest: SuperTest.SuperTest,
+ packageName: SupportedPackage
+) {
+ const pkg = PACKAGES.find(({ name }) => name === packageName);
+ const request = supertest
+ .post('/api/fleet/epm/packages')
+ .set('kbn-xsrf', 'xxx')
+ .set('content-type', 'application/zip');
+
+ return new Promise((resolve, reject) => {
+ createReadStream(path.join(bundledPackagesLocation, `${pkg!.name}-${pkg!.version}.zip`))
+ .on('data', (chunk) => request.write(chunk))
+ .on('end', () => {
+ request
+ .send()
+ .expect(200)
+ .then(() => resolve())
+ .catch(reject);
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/connector_types.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/connector_types.ts
deleted file mode 100644
index 0f0a2e40fba00..0000000000000
--- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/connector_types.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { FtrProviderContext } from '../../../ftr_provider_context';
-
-export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const commonScreenshots = getService('commonScreenshots');
- const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
- const pageObjects = getPageObjects(['common', 'header']);
- const actions = getService('actions');
- const browser = getService('browser');
- const comboBox = getService('comboBox');
- const find = getService('find');
- const testSubjects = getService('testSubjects');
- const testIndex = `test-index`;
- const indexDocument =
- `{\n` +
- `"rule_id": "{{rule.id}}",\n` +
- `"rule_name": "{{rule.name}}",\n` +
- `"alert_id": "{{alert.id}}",\n` +
- `"context_message": "{{context.message}}"\n`;
- const webhookJson =
- `{\n` +
- `"short_description": "{{context.rule.name}}",\n` +
- `"description": "{{context.rule.description}}"`;
- const emailConnectorName = 'my-email-connector';
-
- describe('connector types', function () {
- let emailConnectorId: string;
- before(async () => {
- ({ id: emailConnectorId } = await actions.api.createConnector({
- name: emailConnectorName,
- config: {
- service: 'other',
- from: 'bob@example.com',
- host: 'some.non.existent.com',
- port: 25,
- },
- secrets: {
- user: 'bob',
- password: 'supersecret',
- },
- connectorTypeId: '.email',
- }));
- });
-
- beforeEach(async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- });
-
- after(async () => {
- await actions.api.deleteConnector(emailConnectorId);
- });
-
- it('server log connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('server-log');
- await testSubjects.setValue('nameInput', 'Server log test connector');
- await commonScreenshots.takeScreenshot('serverlog-connector', screenshotDirectories);
- const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn');
- await saveTestButton.click();
- await commonScreenshots.takeScreenshot('serverlog-params-test', screenshotDirectories);
- const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton');
- await flyOutCancelButton.click();
- });
-
- it('index connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('index');
- await testSubjects.setValue('nameInput', 'Index test connector');
- await comboBox.set('connectorIndexesComboBox', testIndex);
- const timeFieldToggle = await testSubjects.find('hasTimeFieldCheckbox');
- await timeFieldToggle.click();
- await commonScreenshots.takeScreenshot('index-connector', screenshotDirectories);
- const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn');
- await saveTestButton.click();
- await testSubjects.setValue('actionJsonEditor', indexDocument);
- await commonScreenshots.takeScreenshot('index-params-test', screenshotDirectories);
- const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton');
- await flyOutCancelButton.click();
- });
-
- it('slack api connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('slack');
- await testSubjects.click('.slack_apiButton');
- await testSubjects.setValue('nameInput', 'Slack api test connector');
- await testSubjects.setValue('secrets.token-input', 'xoxb-XXXX-XXXX-XXXX');
- await commonScreenshots.takeScreenshot('slack-api-connector', screenshotDirectories);
- await testSubjects.click('create-connector-flyout-save-test-btn');
- await testSubjects.click('toastCloseButton');
- await pageObjects.common.closeToast();
- await commonScreenshots.takeScreenshot('slack-api-params', screenshotDirectories);
- await testSubjects.click('euiFlyoutCloseButton');
- });
-
- it('slack webhook connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('slack');
- await testSubjects.setValue('nameInput', 'Slack webhook test connector');
- await testSubjects.setValue(
- 'slackWebhookUrlInput',
- 'https://hooks.slack.com/services/abcd/ljklmnopqrstuvwxz'
- );
- await commonScreenshots.takeScreenshot('slack-webhook-connector', screenshotDirectories);
- await testSubjects.click('create-connector-flyout-save-test-btn');
- await testSubjects.click('toastCloseButton');
- await commonScreenshots.takeScreenshot('slack-webhook-params', screenshotDirectories);
- await testSubjects.click('euiFlyoutCloseButton');
- });
-
- it('email connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('email');
- await testSubjects.setValue('nameInput', 'Gmail connector');
- await testSubjects.setValue('emailFromInput', 'test@gmail.com');
- await testSubjects.setValue('emailServiceSelectInput', 'gmail');
- await commonScreenshots.takeScreenshot('email-connector', screenshotDirectories);
- const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton');
- await flyOutCancelButton.click();
- });
-
- it('test email connector screenshots', async () => {
- const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch');
- await searchBox.click();
- await searchBox.clearValue();
- await searchBox.type('my actionTypeId:(.email)');
- await searchBox.pressKeys(browser.keys.ENTER);
- const connectorList = await testSubjects.find('actionsTable');
- const emailConnector = await connectorList.findByCssSelector(
- `[title="${emailConnectorName}"]`
- );
- await emailConnector.click();
- const testButton = await testSubjects.find('testConnectorTab');
- await testButton.click();
- await testSubjects.setValue('comboBoxSearchInput', 'elastic@gmail.com');
- await testSubjects.setValue('subjectInput', 'Test subject');
- await testSubjects.setValue('messageTextArea', 'Enter message text');
- /* timing issue sometimes happens with the combobox so we just try to set the subjectInput again */
- await testSubjects.setValue('subjectInput', 'Test subject');
- await commonScreenshots.takeScreenshot(
- 'email-params-test',
- screenshotDirectories,
- 1400,
- 1024
- );
- });
-
- it('webhook connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('webhook');
- await testSubjects.setValue('nameInput', 'Webhook test connector');
- await testSubjects.setValue('webhookUrlText', 'https://example.com');
- await testSubjects.setValue('webhookUserInput', 'testuser');
- await testSubjects.setValue('webhookPasswordInput', 'password');
- await commonScreenshots.takeScreenshot('webhook-connector', screenshotDirectories);
- const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn');
- await saveTestButton.click();
- await testSubjects.setValue('actionJsonEditor', webhookJson);
- await commonScreenshots.takeScreenshot('webhook-params-test', screenshotDirectories);
- await testSubjects.click('euiFlyoutCloseButton');
- });
-
- it('pagerduty connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('pagerduty');
- await testSubjects.setValue('nameInput', 'PagerDuty test connector');
- await testSubjects.setValue('pagerdutyApiUrlInput', 'https://dev-test.pagerduty.com/');
- await testSubjects.setValue('pagerdutyRoutingKeyInput', 'testkey');
- await commonScreenshots.takeScreenshot('pagerduty-connector', screenshotDirectories);
- await testSubjects.click('create-connector-flyout-save-test-btn');
- await testSubjects.click('toastCloseButton');
- await testSubjects.setValue('eventActionSelect', 'trigger');
- await commonScreenshots.takeScreenshot('pagerduty-params-test', screenshotDirectories);
- await testSubjects.click('euiFlyoutCloseButton');
- });
-
- it('opsgenie connector screenshots', async () => {
- await pageObjects.common.navigateToApp('connectors');
- await pageObjects.header.waitUntilLoadingHasFinished();
- await actions.common.openNewConnectorForm('opsgenie');
- await testSubjects.setValue('nameInput', 'Opsgenie test connector');
- await testSubjects.setValue('secrets.apiKey-input', 'testkey');
- await commonScreenshots.takeScreenshot('opsgenie-connector', screenshotDirectories);
- await testSubjects.click('create-connector-flyout-save-test-btn');
- await testSubjects.click('toastCloseButton');
- await commonScreenshots.takeScreenshot('opsgenie-params-test', screenshotDirectories);
- await testSubjects.click('euiFlyoutCloseButton');
- });
- });
-}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/email_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/email_connector.ts
new file mode 100644
index 0000000000000..6e3339dbeacef
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/email_connector.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const browser = getService('browser');
+ const find = getService('find');
+ const testSubjects = getService('testSubjects');
+ const emailConnectorName = 'my-email-connector';
+
+ describe('email connector', function () {
+ let emailConnectorId: string;
+ before(async () => {
+ ({ id: emailConnectorId } = await actions.api.createConnector({
+ name: emailConnectorName,
+ config: {
+ service: 'other',
+ from: 'bob@example.com',
+ host: 'some.non.existent.com',
+ port: 25,
+ },
+ secrets: {
+ user: 'bob',
+ password: 'supersecret',
+ },
+ connectorTypeId: '.email',
+ }));
+ });
+
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ after(async () => {
+ await actions.api.deleteConnector(emailConnectorId);
+ });
+
+ it('email connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('email');
+ await testSubjects.setValue('nameInput', 'Gmail connector');
+ await testSubjects.setValue('emailFromInput', 'test@gmail.com');
+ await testSubjects.setValue('emailServiceSelectInput', 'gmail');
+ await commonScreenshots.takeScreenshot('email-connector', screenshotDirectories);
+ const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton');
+ await flyOutCancelButton.click();
+ });
+
+ it('test email connector screenshots', async () => {
+ const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch');
+ await searchBox.click();
+ await searchBox.clearValue();
+ await searchBox.type('my actionTypeId:(.email)');
+ await searchBox.pressKeys(browser.keys.ENTER);
+ const connectorList = await testSubjects.find('actionsTable');
+ const emailConnector = await connectorList.findByCssSelector(
+ `[title="${emailConnectorName}"]`
+ );
+ await emailConnector.click();
+ const testButton = await testSubjects.find('testConnectorTab');
+ await testButton.click();
+ await testSubjects.setValue('comboBoxSearchInput', 'elastic@gmail.com');
+ await testSubjects.setValue('subjectInput', 'Test subject');
+ await testSubjects.setValue('messageTextArea', 'Enter message text');
+ /* timing issue sometimes happens with the combobox so we just try to set the subjectInput again */
+ await testSubjects.setValue('subjectInput', 'Test subject');
+ await commonScreenshots.takeScreenshot(
+ 'email-params-test',
+ screenshotDirectories,
+ 1400,
+ 1024
+ );
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts
index bbfbd62f68b33..69fa2448a7818 100644
--- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/generative_ai_connector.ts
@@ -21,7 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
`"content": "You are a cyber security analyst using Elastic Security. I would like you to evaluate the event below and format your output neatly in markdown syntax. Add your description, an accuracy rating, and a threat rating."\n` +
`}]`;
- describe('connector types', function () {
+ describe('generative ai connector', function () {
beforeEach(async () => {
await pageObjects.common.navigateToApp('connectors');
await pageObjects.header.waitUntilLoadingHasFinished();
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts
index 7c795623c9033..8a75474264676 100644
--- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index.ts
@@ -54,7 +54,15 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) {
});
loadTestFile(require.resolve('./connectors'));
- loadTestFile(require.resolve('./connector_types'));
+ loadTestFile(require.resolve('./email_connector'));
loadTestFile(require.resolve('./generative_ai_connector'));
+ loadTestFile(require.resolve('./index_connector'));
+ loadTestFile(require.resolve('./jira_connector'));
+ loadTestFile(require.resolve('./opsgenie_connector'));
+ loadTestFile(require.resolve('./pagerduty_connector'));
+ loadTestFile(require.resolve('./server_log_connector'));
+ loadTestFile(require.resolve('./slack_connector'));
+ loadTestFile(require.resolve('./webhook_connector'));
+ loadTestFile(require.resolve('./xmatters_connector'));
});
}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index_connector.ts
new file mode 100644
index 0000000000000..838ca43b19724
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/index_connector.ts
@@ -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 { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const comboBox = getService('comboBox');
+ const testSubjects = getService('testSubjects');
+ const testIndex = `test-index`;
+ const indexDocument =
+ `{\n` +
+ `"rule_id": "{{rule.id}}",\n` +
+ `"rule_name": "{{rule.name}}",\n` +
+ `"alert_id": "{{alert.id}}",\n` +
+ `"context_message": "{{context.message}}"\n`;
+
+ describe('index connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('index connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('index');
+ await testSubjects.setValue('nameInput', 'Index test connector');
+ await comboBox.set('connectorIndexesComboBox', testIndex);
+ const timeFieldToggle = await testSubjects.find('hasTimeFieldCheckbox');
+ await timeFieldToggle.click();
+ await commonScreenshots.takeScreenshot('index-connector', screenshotDirectories);
+ const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn');
+ await saveTestButton.click();
+ await testSubjects.setValue('actionJsonEditor', indexDocument);
+ await commonScreenshots.takeScreenshot('index-params-test', screenshotDirectories);
+ const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton');
+ await flyOutCancelButton.click();
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/jira_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/jira_connector.ts
new file mode 100644
index 0000000000000..149ba65183ffc
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/jira_connector.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+
+ describe('jira connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('index connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('jira');
+ await testSubjects.setValue('nameInput', 'Jira test connector');
+ await testSubjects.setValue('config.apiUrl-input', 'https://elastic.atlassian.net');
+ await testSubjects.setValue('config.projectKey-input', 'ES');
+ await testSubjects.setValue('secrets.email-input', 'testuser@example.com');
+ await testSubjects.setValue('secrets.apiToken-input', 'test');
+ await commonScreenshots.takeScreenshot('jira-connector', screenshotDirectories);
+ await testSubjects.click('create-connector-flyout-save-test-btn');
+ await testSubjects.click('toastCloseButton');
+ await commonScreenshots.takeScreenshot('jira-params-test', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/opsgenie_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/opsgenie_connector.ts
new file mode 100644
index 0000000000000..163bc402b8083
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/opsgenie_connector.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+
+ describe('opsgenie connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('opsgenie connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('opsgenie');
+ await testSubjects.setValue('nameInput', 'Opsgenie test connector');
+ await testSubjects.setValue('secrets.apiKey-input', 'testkey');
+ await commonScreenshots.takeScreenshot('opsgenie-connector', screenshotDirectories);
+ await testSubjects.click('create-connector-flyout-save-test-btn');
+ await testSubjects.click('toastCloseButton');
+ await commonScreenshots.takeScreenshot('opsgenie-params-test', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/pagerduty_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/pagerduty_connector.ts
new file mode 100644
index 0000000000000..49c3eac92b340
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/pagerduty_connector.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 { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+
+ describe('pagerduty connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+ it('pagerduty connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('pagerduty');
+ await testSubjects.setValue('nameInput', 'PagerDuty test connector');
+ await testSubjects.setValue('pagerdutyApiUrlInput', 'https://dev-test.pagerduty.com/');
+ await testSubjects.setValue('pagerdutyRoutingKeyInput', 'testkey');
+ await commonScreenshots.takeScreenshot('pagerduty-connector', screenshotDirectories);
+ await testSubjects.click('create-connector-flyout-save-test-btn');
+ await testSubjects.click('toastCloseButton');
+ await testSubjects.setValue('eventActionSelect', 'trigger');
+ await commonScreenshots.takeScreenshot('pagerduty-params-test', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/server_log_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/server_log_connector.ts
new file mode 100644
index 0000000000000..4d3cecb764751
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/server_log_connector.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+
+ describe('server log connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('server log connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('server-log');
+ await testSubjects.setValue('nameInput', 'Server log test connector');
+ await commonScreenshots.takeScreenshot('serverlog-connector', screenshotDirectories);
+ const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn');
+ await saveTestButton.click();
+ await commonScreenshots.takeScreenshot('serverlog-params-test', screenshotDirectories);
+ const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton');
+ await flyOutCancelButton.click();
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/slack_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/slack_connector.ts
new file mode 100644
index 0000000000000..a7f96bbc2eb9e
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/slack_connector.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+
+ describe('slack connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('slack api connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('slack');
+ await testSubjects.click('.slack_apiButton');
+ await testSubjects.setValue('nameInput', 'Slack api test connector');
+ await testSubjects.setValue('secrets.token-input', 'xoxb-XXXX-XXXX-XXXX');
+ await commonScreenshots.takeScreenshot('slack-api-connector', screenshotDirectories);
+ await testSubjects.click('create-connector-flyout-save-test-btn');
+ await testSubjects.click('toastCloseButton');
+ await pageObjects.common.closeToast();
+ await commonScreenshots.takeScreenshot('slack-api-params', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+
+ it('slack webhook connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('slack');
+ await testSubjects.setValue('nameInput', 'Slack webhook test connector');
+ await testSubjects.setValue(
+ 'slackWebhookUrlInput',
+ 'https://hooks.slack.com/services/abcd/ljklmnopqrstuvwxz'
+ );
+ await commonScreenshots.takeScreenshot('slack-webhook-connector', screenshotDirectories);
+ await testSubjects.click('create-connector-flyout-save-test-btn');
+ await testSubjects.click('toastCloseButton');
+ await commonScreenshots.takeScreenshot('slack-webhook-params', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/webhook_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/webhook_connector.ts
new file mode 100644
index 0000000000000..def2a9b0e1dff
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/webhook_connector.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+ const webhookJson =
+ `{\n` +
+ `"short_description": "{{context.rule.name}}",\n` +
+ `"description": "{{context.rule.description}}"`;
+
+ describe('webhook connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('webhook connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('webhook');
+ await testSubjects.setValue('nameInput', 'Webhook test connector');
+ await testSubjects.setValue('webhookUrlText', 'https://example.com');
+ await testSubjects.setValue('webhookUserInput', 'testuser');
+ await testSubjects.setValue('webhookPasswordInput', 'password');
+ await commonScreenshots.takeScreenshot('webhook-connector', screenshotDirectories);
+ const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn');
+ await saveTestButton.click();
+ await testSubjects.setValue('actionJsonEditor', webhookJson);
+ await commonScreenshots.takeScreenshot('webhook-params-test', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ });
+}
diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/xmatters_connector.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/xmatters_connector.ts
new file mode 100644
index 0000000000000..80aafd27dc63b
--- /dev/null
+++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_connectors/xmatters_connector.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const commonScreenshots = getService('commonScreenshots');
+ const screenshotDirectories = ['response_ops_docs', 'stack_connectors'];
+ const pageObjects = getPageObjects(['common', 'header']);
+ const actions = getService('actions');
+ const testSubjects = getService('testSubjects');
+
+ describe('xmatters connector', function () {
+ beforeEach(async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('generative ai connector screenshots', async () => {
+ await pageObjects.common.navigateToApp('connectors');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ await actions.common.openNewConnectorForm('xmatters');
+ await testSubjects.setValue('nameInput', 'xMatters test connector');
+ await commonScreenshots.takeScreenshot('xmatters-connector-basic', screenshotDirectories);
+ const authentication = await testSubjects.find('button-group');
+ // a radio button consists of a div tag that contains an input, a div, and a label
+ // we can't click the input directly, need to click the label
+ const label = await authentication.findByCssSelector('label[title="URL Authentication"]');
+ await label.click();
+ await commonScreenshots.takeScreenshot('xmatters-connector-url', screenshotDirectories);
+ await testSubjects.setValue('secrets.secretsUrl', 'https://example.com');
+ await testSubjects.click('create-connector-flyout-save-test-btn');
+ await testSubjects.click('toastCloseButton');
+ await commonScreenshots.takeScreenshot('xmatters-params-test', screenshotDirectories);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ });
+}
diff --git a/x-pack/test/security_solution_cypress/cypress/README.md b/x-pack/test/security_solution_cypress/cypress/README.md
index 21a6a8a9db493..aa749344201fa 100644
--- a/x-pack/test/security_solution_cypress/cypress/README.md
+++ b/x-pack/test/security_solution_cypress/cypress/README.md
@@ -40,7 +40,12 @@ of data for your test, [**Running the tests**](#running-the-tests) to know how t
Please, before opening a PR with the new test, please make sure that the test fails. If you never see your test fail you don’t know if your test is actually testing the right thing, or testing anything at all.
-Note that we use tags in order to select which tests we want to execute: @serverless, @ess and @brokenInServerless
+Note that we use tags in order to select which tests we want to execute:
+
+- `@serverless` includes a test in the Serverless test suite. You need to explicitly add this tag to any test you want to run against a Serverless environment.
+- `@ess` includes a test in the normal, non-Serverless test suite. You need to explicitly add this tag to any test you want to run against a non-Serverless environment.
+- `@brokenInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Indicates that a test should run in Serverless, but currently is broken.
+- `@skipInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Could indicate many things, e.g. "the test is flaky in Serverless", "the test is Flaky in any type of environemnt", "the test has been temporarily excluded, see the comment above why".
Please, before opening a PR with a new test, make sure that the test fails. If you never see your test fail you don’t know if your test is actually testing the right thing, or testing anything at all.
diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts
index 5ce5af4e639f6..eafcd67682a68 100644
--- a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts
+++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts
@@ -18,7 +18,7 @@ export default defineCypressConfig({
env: {
grepFilterSpecs: true,
grepOmitFiltered: true,
- grepTags: '@serverless --@brokenInServerless',
+ grepTags: '@serverless --@brokenInServerless --@skipInServerless',
},
execTimeout: 150000,
pageLoadTimeout: 150000,
diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts
index d0a89bb34a68a..d172248fb406a 100644
--- a/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts
+++ b/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts
@@ -23,7 +23,7 @@ export default defineCypressConfig({
numTestsKeptInMemory: 10,
env: {
grepFilterSpecs: true,
- grepTags: '@serverless --@brokenInServerless',
+ grepTags: '@serverless --@brokenInServerless --@skipInServerless',
},
e2e: {
experimentalRunAllSpecs: true,
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts
index b9a514b8c2215..f78670bc14b4f 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/create_runtime_field.cy.ts
@@ -25,6 +25,7 @@ import { GET_TIMELINE_HEADER } from '../../screens/timeline';
const alertRunTimeField = 'field.name.alert.page';
const timelineRuntimeField = 'field.name.timeline';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Create DataView runtime field',
{ tags: ['@ess', '@serverless', '@brokenInServerless'] },
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts
index 7ea130b0230d3..77206d714c960 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer.cy.ts
@@ -35,12 +35,14 @@ const rolesToCreate = [secReadCasesAll];
const siemDataViewTitle = 'Security Default Data View';
const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*'];
-describe('Sourcerer', () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Sourcerer', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cy.task('esArchiverResetKibana');
dataViews.forEach((dataView: string) => postDataView(dataView));
});
+ // TODO: https://github.com/elastic/kibana/issues/161539
describe('permissions', { tags: ['@ess', '@brokenInServerless'] }, () => {
before(() => {
createUsersAndRoles(usersToCreate, rolesToCreate);
@@ -52,6 +54,7 @@ describe('Sourcerer', () => {
});
});
+ // TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165766
describe('Default scope', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
beforeEach(() => {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts
index 1476e82421cda..69244ddce38f6 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.cy.ts
@@ -38,7 +38,8 @@ import { closeTimeline, openTimelineById } from '../../tasks/timeline';
const siemDataViewTitle = 'Security Default Data View';
const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*'];
-describe('Timeline scope', { tags: '@brokenInServerless' }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe.skip('Timeline scope', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
beforeEach(() => {
cy.clearLocalStorage();
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts
index 1476e82421cda..979cf8c657b12 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/data_sources/sourcerer_timeline.ts
@@ -38,7 +38,8 @@ import { closeTimeline, openTimelineById } from '../../tasks/timeline';
const siemDataViewTitle = 'Security Default Data View';
const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*'];
-describe('Timeline scope', { tags: '@brokenInServerless' }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Timeline scope', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
beforeEach(() => {
cy.clearLocalStorage();
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts
index 1013a4d7d53d4..48c7f49c14868 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alert_tags.cy.ts
@@ -24,6 +24,7 @@ import {
UNSELECTED_ALERT_TAG,
} from '../../screens/alerts';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe('Alert tagging', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts
index 313d2a625ad9f..2c00882fb15db 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_charts.cy.ts
@@ -24,6 +24,7 @@ import {
} from '../../screens/search_bar';
import { TOASTER } from '../../screens/alerts_detection_rules';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Histogram legend hover actions',
{ tags: ['@ess', '@serverless', '@brokenInServerless'] },
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts
index 69227eb6a7c60..d751f2a68812b 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts
@@ -29,9 +29,10 @@ const waitForPageTitleToBeShown = () => {
cy.get(PAGE_TITLE).should('be.visible');
};
+// TODO: https://github.com/elastic/kibana/issues/161539 Does it need to run in Serverless?
describe(
'Detections > Need Admin Callouts indicating an admin is needed to migrate the alert data set',
- { tags: '@ess' },
+ { tags: ['@ess', '@skipInServerless'] },
() => {
before(() => {
// First, we have to open the app on behalf of a privileged user in order to initialize it.
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts
index 9ab88f2802be8..90226eca02e52 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/cti_enrichments.cy.ts
@@ -27,6 +27,7 @@ import { openJsonView, openThreatIndicatorDetails } from '../../tasks/alerts_det
import { ruleDetailsUrl } from '../../urls/navigation';
import { addsFieldsToTimeline } from '../../tasks/rule_details';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe('CTI Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
@@ -50,6 +51,7 @@ describe('CTI Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless'
);
});
+ // TODO: https://github.com/elastic/kibana/issues/161539
// Skipped: https://github.com/elastic/kibana/issues/162818
it.skip('Displays enrichment matched.* fields on the timeline', () => {
const expectedFields = {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts
index 102405eb95a61..393123650c388 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/enrichments.cy.ts
@@ -30,6 +30,7 @@ import { login, visit } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe('Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts
index 30e0383ff3fa0..a4e74d40dc922 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts
@@ -41,7 +41,8 @@ const waitForPageTitleToBeShown = () => {
cy.get(PAGE_TITLE).should('be.visible');
};
-describe('Detections > Callouts', { tags: '@ess' }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Detections > Callouts', { tags: ['@ess', '@skipInServerless'] }, () => {
before(() => {
// First, we have to open the app on behalf of a privileged user in order to initialize it.
// Otherwise the app will be disabled and show a "welcome"-like page.
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts
index a38ac3b2fb842..7c7cd582baa3b 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_detection.cy.ts
@@ -14,6 +14,7 @@ import { TIMELINE_QUERY, TIMELINE_VIEW_IN_ANALYZER } from '../../screens/timelin
import { selectAlertsHistogram } from '../../tasks/alerts';
import { createTimeline } from '../../tasks/timelines';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Ransomware Detection Alerts',
{ tags: ['@ess', '@serverless', '@brokenInServerless'] },
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts
index c71c3634246a7..8c62fb1929317 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts
@@ -15,6 +15,7 @@ import { selectAlertsHistogram } from '../../tasks/alerts';
import { createTimeline } from '../../tasks/timelines';
import { cleanKibana } from '../../tasks/common';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Ransomware Prevention Alerts',
{ tags: ['@ess', '@serverless', '@brokenInServerless'] },
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts
index 30059c9ad5855..19684d539c287 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts
@@ -55,9 +55,11 @@ const loadPageAsReadOnlyUser = (url: string) => {
waitForPageWithoutDateRange(url, ROLES.reader);
};
+// TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless
+// TODO: https://github.com/elastic/kibana/issues/161540
describe(
'Detection rules, Prebuilt Rules Installation and Update - Authorization/RBAC',
- { tags: '@ess' },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
beforeEach(() => {
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts
index c9fe54f66f0be..b1bdae3ae49f5 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_error_handling.cy.ts
@@ -22,9 +22,10 @@ import {
ruleUpdatesTabClick,
} from '../../../tasks/prebuilt_rules';
+// TODO: https://github.com/elastic/kibana/issues/161540
describe(
'Detection rules, Prebuilt Rules Installation and Update - Error handling',
- { tags: '@ess' },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
beforeEach(() => {
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts
index b6852c698e635..a4fb84d557d3f 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_install_update_workflows.cy.ts
@@ -39,9 +39,10 @@ import {
ruleUpdatesTabClick,
} from '../../../tasks/prebuilt_rules';
+// TODO: https://github.com/elastic/kibana/issues/161540
describe(
'Detection rules, Prebuilt Rules Installation and Update workflow',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
beforeEach(() => {
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts
index b172b84b76953..00e38335792ce 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_management.cy.ts
@@ -50,7 +50,8 @@ const rules = Array.from(Array(5)).map((_, i) => {
});
});
-describe('Prebuilt rules', { tags: ['@ess', '@serverless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe('Prebuilt rules', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cleanKibana();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts
index 9202b77044e93..3ee378f12f3d3 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/prebuilt_rules_notifications.cy.ts
@@ -29,9 +29,10 @@ const RULE_1 = createRuleAssetSavedObject({
rule_id: 'rule_1',
});
+// TODO: https://github.com/elastic/kibana/issues/161540
describe(
'Detection rules, Prebuilt Rules Installation and Update Notifications',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
beforeEach(() => {
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts
index fef7df04844e6..f835f6801dedc 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions/rule_actions.cy.ts
@@ -27,9 +27,10 @@ import { login, visit } from '../../../tasks/login';
import { RULE_CREATION } from '../../../urls/navigation';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Rule actions during detection rule creation',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
const indexConnector = getIndexConnector();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts
index 18455fb116734..31c05a1011800 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts
@@ -115,7 +115,8 @@ import {
import { enablesRule, getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details';
import { ruleDetailsUrl, ruleEditUrl, RULE_CREATION } from '../../../urls/navigation';
-describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Custom query rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
beforeEach(() => {
deleteAlertsAndRules();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts
index da838f54457c3..6d9785ec7ac00 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule_data_view.cy.ts
@@ -67,7 +67,8 @@ import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_deta
import { RULE_CREATION } from '../../../urls/navigation';
-describe('Custom query rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Custom query rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
describe('Custom detection rules creation with data views', () => {
const rule = getDataViewRule();
const expectedUrls = rule.references?.join('');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts
index 747725c88aaa3..5e0dc286a36ac 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_saved_query_rule.cy.ts
@@ -50,7 +50,8 @@ const savedQueryName = 'custom saved query';
const savedQueryQuery = 'process.name: test';
const savedQueryFilterKey = 'testAgent.value';
-describe('Custom saved_query rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Saved query rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts
index 35957cc31938a..545af73b8a065 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts
@@ -56,7 +56,8 @@ import { login, visit } from '../../../tasks/login';
import { RULE_CREATION } from '../../../urls/navigation';
-describe('EQL rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('EQL rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts
index 42dbf0ac0182e..d093e9c37f33d 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/indicator_match_rule.cy.ts
@@ -116,7 +116,8 @@ import {
const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"';
-describe('indicator match', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('indicator match', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
describe('Detection rules, Indicator Match', () => {
const expectedUrls = getNewThreatIndicatorRule().references?.join('');
const expectedFalsePositives = getNewThreatIndicatorRule().false_positives?.join('');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts
index f258ff6c804b7..cb820c60c1202 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts
@@ -53,7 +53,8 @@ import { login, visitWithoutDateRange } from '../../../tasks/login';
import { RULE_CREATION } from '../../../urls/navigation';
-describe('Detection rules, machine learning', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Machine Learning rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
const expectedUrls = (getMachineLearningRule().references ?? []).join('');
const expectedFalsePositives = (getMachineLearningRule().false_positives ?? []).join('');
const expectedTags = (getMachineLearningRule().tags ?? []).join('');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts
index ffcda7468bb61..132b6d71295e8 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/new_terms_rule.cy.ts
@@ -58,7 +58,8 @@ import { login, visit } from '../../../tasks/login';
import { RULE_CREATION } from '../../../urls/navigation';
-describe('New Terms rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('New Terms rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts
index 837d0621cbe6d..46d01f6b83639 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts
@@ -61,7 +61,8 @@ import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_deta
import { RULE_CREATION } from '../../../urls/navigation';
-describe('Detection rules, override', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Rules override', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
const rule = getNewOverrideRule();
const expectedUrls = rule.references?.join('');
const expectedFalsePositives = rule.false_positives?.join('');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts
index 1f93d745884e0..1828455992207 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/threshold_rule.cy.ts
@@ -58,7 +58,8 @@ import { login, visitWithoutDateRange } from '../../../tasks/login';
import { RULE_CREATION } from '../../../urls/navigation';
-describe('Detection rules, threshold', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Threshold rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
const rule = getNewThresholdRule();
const expectedUrls = rule.references?.join('');
const expectedFalsePositives = rule.false_positives?.join('');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts
index dfb598bfa369b..28fce3db9d55b 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts
@@ -24,7 +24,9 @@ import {
} from '../../../../tasks/common/callouts';
import { login, visitSecurityDetectionRulesPage } from '../../../../tasks/login';
-describe('All rules - read only', { tags: '@ess' }, () => {
+// TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe('All rules - read only', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cleanKibana();
createRule(getNewRule({ rule_id: '1', enabled: false }));
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts
index 605ce523eb35b..1ba764c09000d 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/maintenance_windows/maintenance_window_callout.cy.ts
@@ -12,47 +12,52 @@ import { cleanKibana } from '../../../../tasks/common';
import { login, visit } from '../../../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation';
-describe('Maintenance window callout on Rule Management page', { tags: ['@ess'] }, () => {
- let maintenanceWindowId = '';
-
- before(() => {
- cleanKibana();
- login();
-
- const body: AsApiContract = {
- title: 'My maintenance window',
- duration: 60000, // 1 minute
- r_rule: {
- dtstart: new Date().toISOString(),
- tzid: 'Europe/Amsterdam',
- freq: 0,
- count: 1,
- },
- };
-
- // Create a test maintenance window
- cy.request({
- method: 'POST',
- url: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
- headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
- body,
- }).then((response) => {
- maintenanceWindowId = response.body.id;
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe(
+ 'Maintenance window callout on Rule Management page',
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
+ () => {
+ let maintenanceWindowId = '';
+
+ before(() => {
+ cleanKibana();
+ login();
+
+ const body: AsApiContract = {
+ title: 'My maintenance window',
+ duration: 60000, // 1 minute
+ r_rule: {
+ dtstart: new Date().toISOString(),
+ tzid: 'Europe/Amsterdam',
+ freq: 0,
+ count: 1,
+ },
+ };
+
+ // Create a test maintenance window
+ cy.request({
+ method: 'POST',
+ url: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
+ headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
+ body,
+ }).then((response) => {
+ maintenanceWindowId = response.body.id;
+ });
});
- });
-
- after(() => {
- // Delete a test maintenance window
- cy.request({
- method: 'DELETE',
- url: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/${maintenanceWindowId}`,
- headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
+
+ after(() => {
+ // Delete a test maintenance window
+ cy.request({
+ method: 'DELETE',
+ url: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/${maintenanceWindowId}`,
+ headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
+ });
});
- });
- it('Displays the callout when there are running maintenance windows', () => {
- visit(DETECTIONS_RULE_MANAGEMENT_URL);
+ it('Displays the callout when there are running maintenance windows', () => {
+ visit(DETECTIONS_RULE_MANAGEMENT_URL);
- cy.contains('Maintenance window is running');
- });
-});
+ cy.contains('Maintenance window is running');
+ });
+ }
+);
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 f5aaef1b4e841..b115d93dfd7e8 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
@@ -47,7 +47,8 @@ import {
import { ruleDetailsUrl } from '../../../../urls/navigation';
import { enablesRule, waitForPageToBeLoaded } from '../../../../tasks/rule_details';
-describe('Related integrations', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe('Related integrations', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
const DATA_STREAM_NAME = 'logs-related-integrations-test';
const PREBUILT_RULE_NAME = 'Prebuilt rule with related integrations';
const RULE_RELATED_INTEGRATIONS: IntegrationDefinition[] = [
@@ -189,6 +190,7 @@ describe('Related integrations', { tags: ['@ess', '@brokenInServerless'] }, () =
});
});
+ // TODO: https://github.com/elastic/kibana/issues/161540
// Flaky in serverless tests
// @brokenInServerless tag is not working so a skip was needed
describe.skip('rule details', { tags: ['@brokenInServerless'] }, () => {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts
index 96dd999b6e95a..0771f384e132b 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_duplicate_rules.cy.ts
@@ -52,10 +52,11 @@ const EXPIRED_EXCEPTION_ITEM_NAME = 'Sample exception item';
const NON_EXPIRED_EXCEPTION_ITEM_NAME = 'Sample exception item with future expiration';
+// TODO: https://github.com/elastic/kibana/issues/161540
// Flaky on serverless
describe(
'Detection rules, bulk duplicate',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
before(() => {
cleanKibana();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts
index 6b2d3a530cca5..e51bd3a73bde3 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts
@@ -114,550 +114,560 @@ const defaultRuleData = {
timeline_id: '495ad7a7-316e-4544-8a0f-9c098daee76e',
};
-describe('Detection rules, bulk edit', { tags: ['@ess', '@brokenInServerless'] }, () => {
- before(() => {
- cleanKibana();
- });
- beforeEach(() => {
- login();
- // Make sure persisted rules table state is cleared
- resetRulesTableState();
- deleteAlertsAndRules();
- preventPrebuiltRulesPackageInstallation(); // Make sure prebuilt rules aren't pulled from Fleet API
- cy.task('esArchiverResetKibana');
- createRule(getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1', enabled: false }));
- createRule(
- getEqlRule({ ...defaultRuleData, rule_id: '2', name: 'New EQL Rule', enabled: false })
- );
- createRule(
- getMachineLearningRule({
- name: 'New ML Rule Test',
- tags: ['test-default-tag-1', 'test-default-tag-2'],
- enabled: false,
- })
- );
- createRule(
- getNewThreatIndicatorRule({
- ...defaultRuleData,
- rule_id: '4',
- name: 'Threat Indicator Rule Test',
- enabled: false,
- })
- );
- createRule(
- getNewThresholdRule({
- ...defaultRuleData,
- rule_id: '5',
- name: 'Threshold Rule',
- enabled: false,
- })
- );
- createRule(
- getNewTermsRule({ ...defaultRuleData, rule_id: '6', name: 'New Terms Rule', enabled: false })
- );
-
- visitSecurityDetectionRulesPage();
- disableAutoRefresh();
- });
-
- describe('Prerequisites', () => {
- const PREBUILT_RULES = [
- createRuleAssetSavedObject({
- name: 'Prebuilt rule 1',
- rule_id: 'rule_1',
- }),
- createRuleAssetSavedObject({
- name: 'Prebuilt rule 2',
- rule_id: 'rule_2',
- }),
- ];
-
- it('No rules selected', () => {
- openBulkActionsMenu();
-
- // when no rule selected all bulk edit options should be disabled
- cy.get(TAGS_RULE_BULK_MENU_ITEM).should('be.disabled');
- cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).should('be.disabled');
- cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).should('be.disabled');
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe(
+ 'Detection rules, bulk edit',
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ () => {
+ before(() => {
+ cleanKibana();
});
+ beforeEach(() => {
+ login();
+ // Make sure persisted rules table state is cleared
+ resetRulesTableState();
+ deleteAlertsAndRules();
+ preventPrebuiltRulesPackageInstallation(); // Make sure prebuilt rules aren't pulled from Fleet API
+ cy.task('esArchiverResetKibana');
+ createRule(getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1', enabled: false }));
+ createRule(
+ getEqlRule({ ...defaultRuleData, rule_id: '2', name: 'New EQL Rule', enabled: false })
+ );
+ createRule(
+ getMachineLearningRule({
+ name: 'New ML Rule Test',
+ tags: ['test-default-tag-1', 'test-default-tag-2'],
+ enabled: false,
+ })
+ );
+ createRule(
+ getNewThreatIndicatorRule({
+ ...defaultRuleData,
+ rule_id: '4',
+ name: 'Threat Indicator Rule Test',
+ enabled: false,
+ })
+ );
+ createRule(
+ getNewThresholdRule({
+ ...defaultRuleData,
+ rule_id: '5',
+ name: 'Threshold Rule',
+ enabled: false,
+ })
+ );
+ createRule(
+ getNewTermsRule({
+ ...defaultRuleData,
+ rule_id: '6',
+ name: 'New Terms Rule',
+ enabled: false,
+ })
+ );
- it('Only prebuilt rules selected', () => {
- createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES });
-
- // select Elastic(prebuilt) rules, check if we can't proceed further, as Elastic rules are not editable
- filterByElasticRules();
- selectAllRulesOnPage();
- clickApplyTimelineTemplatesMenuItem();
-
- getRulesManagementTableRows().then((rows) => {
- // check modal window for Elastic rule that can't be edited
- checkPrebuiltRulesCannotBeModified(rows.length);
+ visitSecurityDetectionRulesPage();
+ disableAutoRefresh();
+ });
- // the confirm button closes modal
- cy.get(MODAL_CONFIRMATION_BTN).should('have.text', 'Close').click();
- cy.get(MODAL_CONFIRMATION_BODY).should('not.exist');
+ describe('Prerequisites', () => {
+ const PREBUILT_RULES = [
+ createRuleAssetSavedObject({
+ name: 'Prebuilt rule 1',
+ rule_id: 'rule_1',
+ }),
+ createRuleAssetSavedObject({
+ name: 'Prebuilt rule 2',
+ rule_id: 'rule_2',
+ }),
+ ];
+
+ it('No rules selected', () => {
+ openBulkActionsMenu();
+
+ // when no rule selected all bulk edit options should be disabled
+ cy.get(TAGS_RULE_BULK_MENU_ITEM).should('be.disabled');
+ cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).should('be.disabled');
+ cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).should('be.disabled');
});
- });
- it('Prebuilt and custom rules selected: user proceeds with custom rules editing', () => {
- getRulesManagementTableRows().then((existedRulesRows) => {
+ it('Only prebuilt rules selected', () => {
createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES });
- // modal window should show how many rules can be edit, how many not
- selectAllRules();
- clickAddTagsMenuItem();
+ // select Elastic(prebuilt) rules, check if we can't proceed further, as Elastic rules are not editable
+ filterByElasticRules();
+ selectAllRulesOnPage();
+ clickApplyTimelineTemplatesMenuItem();
- waitForMixedRulesBulkEditModal(existedRulesRows.length);
+ getRulesManagementTableRows().then((rows) => {
+ // check modal window for Elastic rule that can't be edited
+ checkPrebuiltRulesCannotBeModified(rows.length);
- getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
- checkPrebuiltRulesCannotBeModified(availablePrebuiltRulesCount);
+ // the confirm button closes modal
+ cy.get(MODAL_CONFIRMATION_BTN).should('have.text', 'Close').click();
+ cy.get(MODAL_CONFIRMATION_BODY).should('not.exist');
});
+ });
- // user can proceed with custom rule editing
- cy.get(MODAL_CONFIRMATION_BTN)
- .should('have.text', `Edit ${existedRulesRows.length} custom rules`)
- .click();
+ it('Prebuilt and custom rules selected: user proceeds with custom rules editing', () => {
+ getRulesManagementTableRows().then((existedRulesRows) => {
+ createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES });
- // action should finish
- typeTags(['test-tag']);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: existedRulesRows.length });
- });
- });
+ // modal window should show how many rules can be edit, how many not
+ selectAllRules();
+ clickAddTagsMenuItem();
- it('Prebuilt and custom rules selected: user cancels action', () => {
- createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES });
+ waitForMixedRulesBulkEditModal(existedRulesRows.length);
- getRulesManagementTableRows().then((rows) => {
- // modal window should show how many rules can be edit, how many not
- selectAllRules();
- clickAddTagsMenuItem();
- waitForMixedRulesBulkEditModal(rows.length);
+ getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
+ checkPrebuiltRulesCannotBeModified(availablePrebuiltRulesCount);
+ });
- checkPrebuiltRulesCannotBeModified(PREBUILT_RULES.length);
+ // user can proceed with custom rule editing
+ cy.get(MODAL_CONFIRMATION_BTN)
+ .should('have.text', `Edit ${existedRulesRows.length} custom rules`)
+ .click();
- // user cancels action and modal disappears
- cancelConfirmationModal();
+ // action should finish
+ typeTags(['test-tag']);
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: existedRulesRows.length });
+ });
});
- });
- it('should not lose rules selection after edit action', () => {
- const rulesToUpdate = [RULE_NAME, 'New EQL Rule', 'New Terms Rule'] as const;
- // Switch to 5 rules per page, to have few pages in pagination(ideal way to test auto refresh and selection of few items)
- setRowsPerPageTo(5);
- // and make the rules order isn't changing (set sorting by rule name) over time if rules are run
- sortByTableColumn('Rule');
- selectRulesByName(rulesToUpdate);
-
- // open add tags form and add 2 new tags
- openBulkEditAddTagsForm();
- typeTags(['new-tag-1']);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rulesToUpdate.length });
-
- testMultipleSelectedRulesLabel(rulesToUpdate.length);
- // check if first four(rulesCount) rules still selected and tags are updated
- for (const ruleName of rulesToUpdate) {
- getRuleRow(ruleName).find(EUI_CHECKBOX).should('be.checked');
- getRuleRow(ruleName)
- .find(RULES_TAGS_POPOVER_BTN)
- .each(($el) => {
- testTagsBadge($el, prePopulatedTags.concat(['new-tag-1']));
- });
- }
- });
- });
+ it('Prebuilt and custom rules selected: user cancels action', () => {
+ createAndInstallMockedPrebuiltRules({ rules: PREBUILT_RULES });
- describe('Tags actions', () => {
- it('Display list of tags in tags select', () => {
- selectAllRules();
+ getRulesManagementTableRows().then((rows) => {
+ // modal window should show how many rules can be edit, how many not
+ selectAllRules();
+ clickAddTagsMenuItem();
+ waitForMixedRulesBulkEditModal(rows.length);
- openBulkEditAddTagsForm();
- openTagsSelect();
+ checkPrebuiltRulesCannotBeModified(PREBUILT_RULES.length);
- cy.get(EUI_FILTER_SELECT_ITEM)
- .should('have.length', prePopulatedTags.length)
- .each(($el, index) => {
- cy.wrap($el).should('have.text', prePopulatedTags[index]);
+ // user cancels action and modal disappears
+ cancelConfirmationModal();
});
- });
+ });
- it('Add tags to custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2'];
- const resultingTags = [...prePopulatedTags, ...tagsToBeAdded];
+ it('should not lose rules selection after edit action', () => {
+ const rulesToUpdate = [RULE_NAME, 'New EQL Rule', 'New Terms Rule'] as const;
+ // Switch to 5 rules per page, to have few pages in pagination(ideal way to test auto refresh and selection of few items)
+ setRowsPerPageTo(5);
+ // and make the rules order isn't changing (set sorting by rule name) over time if rules are run
+ sortByTableColumn('Rule');
+ selectRulesByName(rulesToUpdate);
- // check if only pre-populated tags exist in the tags filter
- checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // open add tags form and add 2 new tags
+ openBulkEditAddTagsForm();
+ typeTags(['new-tag-1']);
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rulesToUpdate.length });
+
+ testMultipleSelectedRulesLabel(rulesToUpdate.length);
+ // check if first four(rulesCount) rules still selected and tags are updated
+ for (const ruleName of rulesToUpdate) {
+ getRuleRow(ruleName).find(EUI_CHECKBOX).should('be.checked');
+ getRuleRow(ruleName)
+ .find(RULES_TAGS_POPOVER_BTN)
+ .each(($el) => {
+ testTagsBadge($el, prePopulatedTags.concat(['new-tag-1']));
+ });
+ }
+ });
+ });
+ describe('Tags actions', () => {
+ it('Display list of tags in tags select', () => {
selectAllRules();
- // open add tags form and add 2 new tags
openBulkEditAddTagsForm();
- typeTags(tagsToBeAdded);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ openTagsSelect();
+
+ cy.get(EUI_FILTER_SELECT_ITEM)
+ .should('have.length', prePopulatedTags.length)
+ .each(($el, index) => {
+ cy.wrap($el).should('have.text', prePopulatedTags[index]);
+ });
+ });
- // check if all rules have been updated with new tags
- testAllTagsBadges(resultingTags);
+ it('Add tags to custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2'];
+ const resultingTags = [...prePopulatedTags, ...tagsToBeAdded];
- // check that new tags were added to tags filter
- // tags in tags filter sorted alphabetically
- const resultingTagsInFilter = [...resultingTags].sort();
- checkTagsInTagsFilter(resultingTagsInFilter, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // check if only pre-populated tags exist in the tags filter
+ checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+
+ selectAllRules();
+
+ // open add tags form and add 2 new tags
+ openBulkEditAddTagsForm();
+ typeTags(tagsToBeAdded);
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
+
+ // check if all rules have been updated with new tags
+ testAllTagsBadges(resultingTags);
+
+ // check that new tags were added to tags filter
+ // tags in tags filter sorted alphabetically
+ const resultingTagsInFilter = [...resultingTags].sort();
+ checkTagsInTagsFilter(resultingTagsInFilter, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ });
});
- });
- it('Display success toast after adding tags', () => {
- getRulesManagementTableRows().then((rows) => {
- const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2'];
+ it('Display success toast after adding tags', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2'];
- // check if only pre-populated tags exist in the tags filter
- checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // check if only pre-populated tags exist in the tags filter
+ checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
- selectAllRules();
+ selectAllRules();
- // open add tags form and add 2 new tags
- openBulkEditAddTagsForm();
- typeTags(tagsToBeAdded);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ // open add tags form and add 2 new tags
+ openBulkEditAddTagsForm();
+ typeTags(tagsToBeAdded);
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ });
});
- });
- it('Overwrite tags in custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- const tagsToOverwrite = ['overwrite-tag-1'];
+ it('Overwrite tags in custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const tagsToOverwrite = ['overwrite-tag-1'];
- // check if only pre-populated tags exist in the tags filter
- checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // check if only pre-populated tags exist in the tags filter
+ checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
- selectAllRules();
+ selectAllRules();
- // open add tags form, check overwrite tags and warning message, type tags
- openBulkEditAddTagsForm();
- checkOverwriteTagsCheckbox();
+ // open add tags form, check overwrite tags and warning message, type tags
+ openBulkEditAddTagsForm();
+ checkOverwriteTagsCheckbox();
- cy.get(RULES_BULK_EDIT_TAGS_WARNING).should(
- 'have.text',
- `You’re about to overwrite tags for ${rows.length} selected rules, press Save to apply changes.`
- );
+ cy.get(RULES_BULK_EDIT_TAGS_WARNING).should(
+ 'have.text',
+ `You’re about to overwrite tags for ${rows.length} selected rules, press Save to apply changes.`
+ );
- typeTags(tagsToOverwrite);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ typeTags(tagsToOverwrite);
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
- // check if all rules have been updated with new tags
- testAllTagsBadges(tagsToOverwrite);
+ // check if all rules have been updated with new tags
+ testAllTagsBadges(tagsToOverwrite);
- // check that only new tags are in the tag filter
- checkTagsInTagsFilter(tagsToOverwrite, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // check that only new tags are in the tag filter
+ checkTagsInTagsFilter(tagsToOverwrite, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ });
});
- });
- it('Delete tags from custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- const tagsToDelete = prePopulatedTags.slice(0, 1);
- const resultingTags = prePopulatedTags.slice(1);
+ it('Delete tags from custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const tagsToDelete = prePopulatedTags.slice(0, 1);
+ const resultingTags = prePopulatedTags.slice(1);
- // check if only pre-populated tags exist in the tags filter
- checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // check if only pre-populated tags exist in the tags filter
+ checkTagsInTagsFilter(prePopulatedTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
- selectAllRules();
+ selectAllRules();
- // open add tags form, check overwrite tags, type tags
- openBulkEditDeleteTagsForm();
- typeTags(tagsToDelete);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ // open add tags form, check overwrite tags, type tags
+ openBulkEditDeleteTagsForm();
+ typeTags(tagsToDelete);
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
- // check tags has been removed from all rules
- testAllTagsBadges(resultingTags);
+ // check tags has been removed from all rules
+ testAllTagsBadges(resultingTags);
- // check that tags were removed from the tag filter
- checkTagsInTagsFilter(resultingTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ // check that tags were removed from the tag filter
+ checkTagsInTagsFilter(resultingTags, EUI_SELECTABLE_LIST_ITEM_SR_TEXT);
+ });
});
});
- });
- describe('Index patterns', () => {
- it('Index pattern action applied to custom rules, including machine learning: user proceeds with edit of custom non machine learning rule', () => {
- getRulesManagementTableRows().then((rows) => {
- const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
- const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded];
+ describe('Index patterns', () => {
+ it('Index pattern action applied to custom rules, including machine learning: user proceeds with edit of custom non machine learning rule', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
+ const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded];
+
+ selectAllRules();
+ clickAddIndexPatternsMenuItem();
+
+ // confirm editing custom rules, that are not Machine Learning
+ checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited);
+ cy.get(MODAL_CONFIRMATION_BTN).click();
+
+ typeIndexPatterns(indexPattersToBeAdded);
+ submitBulkEditForm();
+
+ waitForBulkEditActionToFinish({
+ updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited,
+ });
+
+ // check if rule has been updated
+ goToRuleDetailsOf(RULE_NAME);
+ hasIndexPatterns(resultingIndexPatterns.join(''));
+ });
+ });
+ it('Index pattern action applied to custom rules, including machine learning: user cancels action', () => {
selectAllRules();
clickAddIndexPatternsMenuItem();
// confirm editing custom rules, that are not Machine Learning
checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited);
- cy.get(MODAL_CONFIRMATION_BTN).click();
- typeIndexPatterns(indexPattersToBeAdded);
- submitBulkEditForm();
-
- waitForBulkEditActionToFinish({
- updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited,
- });
-
- // check if rule has been updated
- goToRuleDetailsOf(RULE_NAME);
- hasIndexPatterns(resultingIndexPatterns.join(''));
+ // user cancels action and modal disappears
+ cancelConfirmationModal();
});
- });
- it('Index pattern action applied to custom rules, including machine learning: user cancels action', () => {
- selectAllRules();
- clickAddIndexPatternsMenuItem();
-
- // confirm editing custom rules, that are not Machine Learning
- checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited);
+ it('Add index patterns to custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
+ const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded];
+
+ // select only rules that are not ML
+ selectRulesByName([
+ RULE_NAME,
+ 'New EQL Rule',
+ 'Threat Indicator Rule Test',
+ 'Threshold Rule',
+ 'New Terms Rule',
+ ]);
+
+ openBulkEditAddIndexPatternsForm();
+ typeIndexPatterns(indexPattersToBeAdded);
+ submitBulkEditForm();
+
+ waitForBulkEditActionToFinish({
+ updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited,
+ });
- // user cancels action and modal disappears
- cancelConfirmationModal();
- });
+ // check if rule has been updated
+ goToRuleDetailsOf(RULE_NAME);
+ hasIndexPatterns(resultingIndexPatterns.join(''));
+ });
+ });
- it('Add index patterns to custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
- const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded];
+ it('Display success toast after editing the index pattern', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
+
+ // select only rules that are not ML
+ selectRulesByName([
+ RULE_NAME,
+ 'New EQL Rule',
+ 'Threat Indicator Rule Test',
+ 'Threshold Rule',
+ 'New Terms Rule',
+ ]);
+
+ openBulkEditAddIndexPatternsForm();
+ typeIndexPatterns(indexPattersToBeAdded);
+ submitBulkEditForm();
+
+ waitForBulkEditActionToFinish({
+ updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited,
+ });
+ });
+ });
- // select only rules that are not ML
- selectRulesByName([
+ it('Overwrite index patterns in custom rules', () => {
+ const rulesToSelect = [
RULE_NAME,
'New EQL Rule',
'Threat Indicator Rule Test',
'Threshold Rule',
'New Terms Rule',
- ]);
+ ] as const;
+ const indexPattersToWrite = ['index-to-write-1-*', 'index-to-write-2-*'];
+
+ // select only rules that are not ML
+ selectRulesByName(rulesToSelect);
openBulkEditAddIndexPatternsForm();
- typeIndexPatterns(indexPattersToBeAdded);
+
+ // check overwrite index patterns checkbox, ensure warning message is displayed and type index patterns
+ checkOverwriteIndexPatternsCheckbox();
+ cy.get(RULES_BULK_EDIT_INDEX_PATTERNS_WARNING).should(
+ 'have.text',
+ `You’re about to overwrite index patterns for ${rulesToSelect.length} selected rules, press Save to apply changes.`
+ );
+
+ typeIndexPatterns(indexPattersToWrite);
submitBulkEditForm();
- waitForBulkEditActionToFinish({
- updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited,
- });
+ waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length });
// check if rule has been updated
goToRuleDetailsOf(RULE_NAME);
- hasIndexPatterns(resultingIndexPatterns.join(''));
+ hasIndexPatterns(indexPattersToWrite.join(''));
});
- });
-
- it('Display success toast after editing the index pattern', () => {
- getRulesManagementTableRows().then((rows) => {
- const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
- // select only rules that are not ML
- selectRulesByName([
+ it('Delete index patterns from custom rules', () => {
+ const rulesToSelect = [
RULE_NAME,
'New EQL Rule',
'Threat Indicator Rule Test',
'Threshold Rule',
'New Terms Rule',
- ]);
-
- openBulkEditAddIndexPatternsForm();
- typeIndexPatterns(indexPattersToBeAdded);
- submitBulkEditForm();
-
- waitForBulkEditActionToFinish({
- updatedCount: rows.length - expectedNumberOfMachineLearningRulesToBeEdited,
- });
- });
- });
+ ] as const;
+ const indexPatternsToDelete = prePopulatedIndexPatterns.slice(0, 1);
+ const resultingIndexPatterns = prePopulatedIndexPatterns.slice(1);
- it('Overwrite index patterns in custom rules', () => {
- const rulesToSelect = [
- RULE_NAME,
- 'New EQL Rule',
- 'Threat Indicator Rule Test',
- 'Threshold Rule',
- 'New Terms Rule',
- ] as const;
- const indexPattersToWrite = ['index-to-write-1-*', 'index-to-write-2-*'];
-
- // select only rules that are not ML
- selectRulesByName(rulesToSelect);
-
- openBulkEditAddIndexPatternsForm();
-
- // check overwrite index patterns checkbox, ensure warning message is displayed and type index patterns
- checkOverwriteIndexPatternsCheckbox();
- cy.get(RULES_BULK_EDIT_INDEX_PATTERNS_WARNING).should(
- 'have.text',
- `You’re about to overwrite index patterns for ${rulesToSelect.length} selected rules, press Save to apply changes.`
- );
+ // select only not ML rules
+ selectRulesByName(rulesToSelect);
- typeIndexPatterns(indexPattersToWrite);
- submitBulkEditForm();
+ openBulkEditDeleteIndexPatternsForm();
+ typeIndexPatterns(indexPatternsToDelete);
+ submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length });
+ waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length });
- // check if rule has been updated
- goToRuleDetailsOf(RULE_NAME);
- hasIndexPatterns(indexPattersToWrite.join(''));
- });
-
- it('Delete index patterns from custom rules', () => {
- const rulesToSelect = [
- RULE_NAME,
- 'New EQL Rule',
- 'Threat Indicator Rule Test',
- 'Threshold Rule',
- 'New Terms Rule',
- ] as const;
- const indexPatternsToDelete = prePopulatedIndexPatterns.slice(0, 1);
- const resultingIndexPatterns = prePopulatedIndexPatterns.slice(1);
-
- // select only not ML rules
- selectRulesByName(rulesToSelect);
-
- openBulkEditDeleteIndexPatternsForm();
- typeIndexPatterns(indexPatternsToDelete);
- submitBulkEditForm();
-
- waitForBulkEditActionToFinish({ updatedCount: rulesToSelect.length });
-
- // check if rule has been updated
- goToRuleDetailsOf(RULE_NAME);
- hasIndexPatterns(resultingIndexPatterns.join(''));
- });
+ // check if rule has been updated
+ goToRuleDetailsOf(RULE_NAME);
+ hasIndexPatterns(resultingIndexPatterns.join(''));
+ });
- it('Delete all index patterns from custom rules', () => {
- const rulesToSelect = [
- RULE_NAME,
- 'New EQL Rule',
- 'Threat Indicator Rule Test',
- 'Threshold Rule',
- 'New Terms Rule',
- ] as const;
+ it('Delete all index patterns from custom rules', () => {
+ const rulesToSelect = [
+ RULE_NAME,
+ 'New EQL Rule',
+ 'Threat Indicator Rule Test',
+ 'Threshold Rule',
+ 'New Terms Rule',
+ ] as const;
- // select only rules that are not ML
- selectRulesByName(rulesToSelect);
+ // select only rules that are not ML
+ selectRulesByName(rulesToSelect);
- openBulkEditDeleteIndexPatternsForm();
- typeIndexPatterns(prePopulatedIndexPatterns);
- submitBulkEditForm();
+ openBulkEditDeleteIndexPatternsForm();
+ typeIndexPatterns(prePopulatedIndexPatterns);
+ submitBulkEditForm();
- // error toast should be displayed that that rules edit failed
- waitForBulkEditActionToFinish({ failedCount: rulesToSelect.length });
+ // error toast should be displayed that that rules edit failed
+ waitForBulkEditActionToFinish({ failedCount: rulesToSelect.length });
- // on error toast button click display error that index patterns can't be empty
- clickErrorToastBtn();
- cy.contains(MODAL_ERROR_BODY, "Index patterns can't be empty");
+ // on error toast button click display error that index patterns can't be empty
+ clickErrorToastBtn();
+ cy.contains(MODAL_ERROR_BODY, "Index patterns can't be empty");
+ });
});
- });
- describe('Timeline templates', () => {
- beforeEach(() => {
- loadPrepackagedTimelineTemplates();
- });
+ describe('Timeline templates', () => {
+ beforeEach(() => {
+ loadPrepackagedTimelineTemplates();
+ });
- it('Apply timeline template to custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- const timelineTemplateName = 'Generic Endpoint Timeline';
+ it('Apply timeline template to custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const timelineTemplateName = 'Generic Endpoint Timeline';
- selectAllRules();
+ selectAllRules();
- // open Timeline template form, check warning, select timeline template
- clickApplyTimelineTemplatesMenuItem();
- cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING).contains(
- `You're about to apply changes to ${rows.length} selected rules. If you previously applied Timeline templates to these rules, they will be overwritten or (if you select 'None') reset to none.`
- );
- selectTimelineTemplate(timelineTemplateName);
+ // open Timeline template form, check warning, select timeline template
+ clickApplyTimelineTemplatesMenuItem();
+ cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING).contains(
+ `You're about to apply changes to ${rows.length} selected rules. If you previously applied Timeline templates to these rules, they will be overwritten or (if you select 'None') reset to none.`
+ );
+ selectTimelineTemplate(timelineTemplateName);
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
- // check if timeline template has been updated to selected one
- goToRuleDetailsOf(RULE_NAME);
- getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', timelineTemplateName);
+ // check if timeline template has been updated to selected one
+ goToRuleDetailsOf(RULE_NAME);
+ getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', timelineTemplateName);
+ });
});
- });
- it('Reset timeline template to None for custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- const noneTimelineTemplate = 'None';
+ it('Reset timeline template to None for custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ const noneTimelineTemplate = 'None';
- selectAllRules();
+ selectAllRules();
- // open Timeline template form, submit form without picking timeline template as None is selected by default
- clickApplyTimelineTemplatesMenuItem();
+ // open Timeline template form, submit form without picking timeline template as None is selected by default
+ clickApplyTimelineTemplatesMenuItem();
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
- // check if timeline template has been updated to selected one, by opening rule that have had timeline prior to editing
- goToRuleDetailsOf(RULE_NAME);
- getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', noneTimelineTemplate);
+ // check if timeline template has been updated to selected one, by opening rule that have had timeline prior to editing
+ goToRuleDetailsOf(RULE_NAME);
+ getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', noneTimelineTemplate);
+ });
});
});
- });
- describe('Schedule', () => {
- it('Default values are applied to bulk edit schedule fields', () => {
- getRulesManagementTableRows().then((rows) => {
- selectAllRules();
- clickUpdateScheduleMenuItem();
+ describe('Schedule', () => {
+ it('Default values are applied to bulk edit schedule fields', () => {
+ getRulesManagementTableRows().then((rows) => {
+ selectAllRules();
+ clickUpdateScheduleMenuItem();
- assertUpdateScheduleWarningExists(rows.length);
+ assertUpdateScheduleWarningExists(rows.length);
- assertDefaultValuesAreAppliedToScheduleFields({
- interval: 5,
- lookback: 1,
+ assertDefaultValuesAreAppliedToScheduleFields({
+ interval: 5,
+ lookback: 1,
+ });
});
});
- });
- it('Updates schedule for custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- selectAllRules();
- clickUpdateScheduleMenuItem();
+ it('Updates schedule for custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ selectAllRules();
+ clickUpdateScheduleMenuItem();
- assertUpdateScheduleWarningExists(rows.length);
+ assertUpdateScheduleWarningExists(rows.length);
- typeScheduleInterval('20');
- setScheduleIntervalTimeUnit('Hours');
+ typeScheduleInterval('20');
+ setScheduleIntervalTimeUnit('Hours');
- typeScheduleLookback('10');
- setScheduleLookbackTimeUnit('Minutes');
+ typeScheduleLookback('10');
+ setScheduleLookbackTimeUnit('Minutes');
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
- goToRuleDetailsOf(RULE_NAME);
+ goToRuleDetailsOf(RULE_NAME);
- assertRuleScheduleValues({
- interval: '20h',
- lookback: '10m',
+ assertRuleScheduleValues({
+ interval: '20h',
+ lookback: '10m',
+ });
});
});
- });
- it('Validates invalid inputs when scheduling for custom rules', () => {
- getRulesManagementTableRows().then((rows) => {
- selectAllRules();
- clickUpdateScheduleMenuItem();
+ it('Validates invalid inputs when scheduling for custom rules', () => {
+ getRulesManagementTableRows().then((rows) => {
+ selectAllRules();
+ clickUpdateScheduleMenuItem();
- // Validate invalid values are corrected to minimumValue - for 0 and negative values
- typeScheduleInterval('0');
- setScheduleIntervalTimeUnit('Hours');
+ // Validate invalid values are corrected to minimumValue - for 0 and negative values
+ typeScheduleInterval('0');
+ setScheduleIntervalTimeUnit('Hours');
- typeScheduleLookback('-5');
- setScheduleLookbackTimeUnit('Seconds');
+ typeScheduleLookback('-5');
+ setScheduleLookbackTimeUnit('Seconds');
- submitBulkEditForm();
- waitForBulkEditActionToFinish({ updatedCount: rows.length });
+ submitBulkEditForm();
+ waitForBulkEditActionToFinish({ updatedCount: rows.length });
- goToRuleDetailsOf(RULE_NAME);
+ goToRuleDetailsOf(RULE_NAME);
- assertRuleScheduleValues({
- interval: '1h',
- lookback: '1s',
+ assertRuleScheduleValues({
+ interval: '1h',
+ lookback: '1s',
+ });
});
});
});
- });
-});
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts
index 997ded389422e..a2575f825dd75 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts
@@ -73,9 +73,10 @@ const ruleNameToAssert = 'Custom rule name with actions';
const expectedExistingSlackMessage = 'Existing slack action';
const expectedSlackMessage = 'Slack action test message';
+// TODO: https://github.com/elastic/kibana/issues/161540
describe(
'Detection rules, bulk edit of rule actions',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
beforeEach(() => {
cleanKibana();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts
index b8b26df371ed0..23e708a47462f 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_data_view.cy.ts
@@ -52,9 +52,10 @@ const DATA_VIEW_ID = 'auditbeat';
const expectedIndexPatterns = ['index-1-*', 'index-2-*'];
+// TODO: https://github.com/elastic/kibana/issues/161540
describe(
'Bulk editing index patterns of rules with a data view only',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
const TESTED_CUSTOM_QUERY_RULE_DATA = getNewRule({
index: undefined,
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts
index a9c6d6d693129..050c944c42d2d 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts
@@ -55,7 +55,8 @@ const prebuiltRules = Array.from(Array(7)).map((_, i) => {
});
});
-describe('Export rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe('Export rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
const downloadsFolder = Cypress.config('downloadsFolder');
before(() => {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts
index f42dabbbc030b..147148b724d7e 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/import_rules.cy.ts
@@ -17,7 +17,8 @@ import { login, visitWithoutDateRange } from '../../../../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../../urls/navigation';
const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson';
-describe('Import rules', { tags: ['@ess', '@brokenInServerless'] }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe('Import rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts
index 6f5dc717c8607..b5121bda97b0e 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts
@@ -47,8 +47,9 @@ import { TOOLTIP } from '../../../../../screens/common';
const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson';
+// TODO: https://github.com/elastic/kibana/issues/161540
// Flaky in serverless tests
-describe('rule snoozing', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
+describe('rule snoozing', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cleanKibana();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts
index 4d38fbda5525b..ffcfb468e0824 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts
@@ -33,86 +33,91 @@ import { getNewRule } from '../../../../objects/rule';
const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000;
const NUM_OF_TEST_RULES = 6;
-describe('Rules table: auto-refresh', { tags: ['@ess', '@brokenInServerless'] }, () => {
- before(() => {
- cleanKibana();
- login();
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe(
+ 'Rules table: auto-refresh',
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ () => {
+ before(() => {
+ cleanKibana();
+ login();
- for (let i = 1; i <= NUM_OF_TEST_RULES; ++i) {
- createRule(getNewRule({ name: `Test rule ${i}`, rule_id: `${i}`, enabled: false }));
- }
- });
+ for (let i = 1; i <= NUM_OF_TEST_RULES; ++i) {
+ createRule(getNewRule({ name: `Test rule ${i}`, rule_id: `${i}`, enabled: false }));
+ }
+ });
- beforeEach(() => {
- login();
- });
+ beforeEach(() => {
+ login();
+ });
- it('Auto refreshes rules', () => {
- mockGlobalClock();
- visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
+ it('Auto refreshes rules', () => {
+ mockGlobalClock();
+ visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
- expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
+ expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
- // // mock 1 minute passing to make sure refresh is conducted
- cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist');
- cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE);
- cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('be.visible');
+ // // mock 1 minute passing to make sure refresh is conducted
+ cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist');
+ cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE);
+ cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('be.visible');
- cy.contains(REFRESH_RULES_STATUS, 'Updated now');
- });
+ cy.contains(REFRESH_RULES_STATUS, 'Updated now');
+ });
- it('should prevent table from rules refetch if any rule selected', () => {
- mockGlobalClock();
- visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
+ it('should prevent table from rules refetch if any rule selected', () => {
+ mockGlobalClock();
+ visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
- expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
+ expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
- selectRulesByName(['Test rule 1']);
+ selectRulesByName(['Test rule 1']);
- // mock 1 minute passing to make sure refresh is not conducted
- cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist');
- cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE);
- cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist');
+ // mock 1 minute passing to make sure refresh is not conducted
+ cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist');
+ cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE);
+ cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist');
- // ensure rule is still selected
- getRuleRow('Test rule 1').find(EUI_CHECKBOX).should('be.checked');
+ // ensure rule is still selected
+ getRuleRow('Test rule 1').find(EUI_CHECKBOX).should('be.checked');
- cy.get(REFRESH_RULES_STATUS).should('have.not.text', 'Updated now');
- });
+ cy.get(REFRESH_RULES_STATUS).should('have.not.text', 'Updated now');
+ });
- it('should disable auto refresh when any rule selected and enable it after rules unselected', () => {
- visit(DETECTIONS_RULE_MANAGEMENT_URL);
+ it('should disable auto refresh when any rule selected and enable it after rules unselected', () => {
+ visit(DETECTIONS_RULE_MANAGEMENT_URL);
- expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
+ expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
- // check refresh settings if it's enabled before selecting
- expectAutoRefreshIsEnabled();
+ // check refresh settings if it's enabled before selecting
+ expectAutoRefreshIsEnabled();
- selectAllRules();
+ selectAllRules();
- // auto refresh should be deactivated (which means disabled without an ability to enable it) after rules selected
- expectAutoRefreshIsDeactivated();
+ // auto refresh should be deactivated (which means disabled without an ability to enable it) after rules selected
+ expectAutoRefreshIsDeactivated();
- clearAllRuleSelection();
+ clearAllRuleSelection();
- // after all rules unselected, auto refresh should be reset to its previous state
- expectAutoRefreshIsEnabled();
- });
+ // after all rules unselected, auto refresh should be reset to its previous state
+ expectAutoRefreshIsEnabled();
+ });
- it('should not enable auto refresh after rules were unselected if auto refresh was disabled', () => {
- visit(DETECTIONS_RULE_MANAGEMENT_URL);
+ it('should not enable auto refresh after rules were unselected if auto refresh was disabled', () => {
+ visit(DETECTIONS_RULE_MANAGEMENT_URL);
- expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
+ expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES);
- disableAutoRefresh();
+ disableAutoRefresh();
- selectAllRules();
+ selectAllRules();
- expectAutoRefreshIsDeactivated();
+ expectAutoRefreshIsDeactivated();
- clearAllRuleSelection();
+ clearAllRuleSelection();
- // after all rules unselected, auto refresh should still be disabled
- expectAutoRefreshIsDisabled();
- });
-});
+ // after all rules unselected, auto refresh should still be disabled
+ expectAutoRefreshIsDisabled();
+ });
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts
index 97ccd8494e3dd..d6cf2c415297c 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_filtering.cy.ts
@@ -28,8 +28,9 @@ import {
import { disableAutoRefresh } from '../../../../tasks/alerts_detection_rules';
import { getNewRule } from '../../../../objects/rule';
+// TODO: https://github.com/elastic/kibana/issues/161540
// Flaky in serverless tests
-describe('Rules table: filtering', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
+describe('Rules table: filtering', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cleanKibana();
});
@@ -42,6 +43,7 @@ describe('Rules table: filtering', { tags: ['@ess', '@serverless', '@brokenInSer
cy.task('esArchiverResetKibana');
});
+ // TODO: https://github.com/elastic/kibana/issues/161540
describe.skip('Last response filter', () => {
// Flaky in serverless tests
// @brokenInServerless tag is not working so a skip was needed
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts
index d2fbd5433f872..a568bbefe7fae 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_links.cy.ts
@@ -12,8 +12,9 @@ import { cleanKibana, deleteAlertsAndRules } from '../../../../tasks/common';
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation';
+// TODO: https://github.com/elastic/kibana/issues/161540
// Flaky in serverless tests
-describe('Rules table: links', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
+describe('Rules table: links', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cleanKibana();
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts
index d2b2f93200e26..c2b2d6e746a00 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_persistent_state.cy.ts
@@ -99,310 +99,315 @@ function expectDefaultRulesTableState(): void {
expectTablePage(1);
}
-describe('Rules table: persistent state', { tags: ['@ess', '@serverless'] }, () => {
- before(() => {
- cleanKibana();
- createTestRules();
- });
-
- beforeEach(() => {
- login();
- resetRulesTableState();
- });
-
- // Flaky on serverless
- // FLAKY: https://github.com/elastic/kibana/issues/165740
- describe(
- 'while on a happy path',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
- () => {
- it('activates management tab by default', () => {
- visit(SECURITY_DETECTIONS_RULES_URL);
-
- expectRulesManagementTab();
- });
+// TODO: https://github.com/elastic/kibana/issues/161540
+describe(
+ 'Rules table: persistent state',
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
+ () => {
+ before(() => {
+ cleanKibana();
+ createTestRules();
+ });
- it('leads to displaying a rule according to the specified filters', () => {
- visitRulesTableWithState({
- searchTerm: 'rule',
- tags: ['tag-b'],
- source: 'custom',
- enabled: false,
- field: 'name',
- order: 'asc',
- perPage: 5,
- page: 2,
- });
+ beforeEach(() => {
+ login();
+ resetRulesTableState();
+ });
- expectManagementTableRules(['rule 6']);
- });
+ // Flaky on serverless
+ // FLAKY: https://github.com/elastic/kibana/issues/165740
+ describe(
+ 'while on a happy path',
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ () => {
+ it('activates management tab by default', () => {
+ visit(SECURITY_DETECTIONS_RULES_URL);
- it('loads from the url', () => {
- visitRulesTableWithState({
- searchTerm: 'rule',
- tags: ['tag-b'],
- source: 'custom',
- enabled: false,
- field: 'name',
- order: 'asc',
- perPage: 5,
- page: 2,
+ expectRulesManagementTab();
});
- expectRulesManagementTab();
- expectFilterSearchTerm('rule');
- expectFilterByTags(['tag-b']);
- expectFilterByCustomRules();
- expectFilterByDisabledRules();
- expectTableSorting('Rule', 'asc');
- expectRowsPerPage(5);
- expectTablePage(2);
- });
-
- it('loads from the session storage', () => {
- setStorageState({
- searchTerm: 'test',
- tags: ['tag-a'],
- source: 'prebuilt',
- enabled: true,
- field: 'severity',
- order: 'desc',
- perPage: 10,
+ it('leads to displaying a rule according to the specified filters', () => {
+ visitRulesTableWithState({
+ searchTerm: 'rule',
+ tags: ['tag-b'],
+ source: 'custom',
+ enabled: false,
+ field: 'name',
+ order: 'asc',
+ perPage: 5,
+ page: 2,
+ });
+
+ expectManagementTableRules(['rule 6']);
});
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ it('loads from the url', () => {
+ visitRulesTableWithState({
+ searchTerm: 'rule',
+ tags: ['tag-b'],
+ source: 'custom',
+ enabled: false,
+ field: 'name',
+ order: 'asc',
+ perPage: 5,
+ page: 2,
+ });
- expectRulesManagementTab();
- expectFilterSearchTerm('test');
- expectFilterByTags(['tag-a']);
- expectFilterByPrebuiltRules();
- expectFilterByEnabledRules();
- expectTableSorting('Severity', 'desc');
- });
-
- it('prefers url state over storage state', () => {
- setStorageState({
- searchTerm: 'test',
- tags: ['tag-c'],
- source: 'prebuilt',
- enabled: true,
- field: 'severity',
- order: 'desc',
- perPage: 10,
- });
-
- visitRulesTableWithState({
- searchTerm: 'rule',
- tags: ['tag-b'],
- source: 'custom',
- enabled: false,
- field: 'name',
- order: 'asc',
- perPage: 5,
- page: 2,
+ expectRulesManagementTab();
+ expectFilterSearchTerm('rule');
+ expectFilterByTags(['tag-b']);
+ expectFilterByCustomRules();
+ expectFilterByDisabledRules();
+ expectTableSorting('Rule', 'asc');
+ expectRowsPerPage(5);
+ expectTablePage(2);
});
- expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(2);
- });
+ it('loads from the session storage', () => {
+ setStorageState({
+ searchTerm: 'test',
+ tags: ['tag-a'],
+ source: 'prebuilt',
+ enabled: true,
+ field: 'severity',
+ order: 'desc',
+ perPage: 10,
+ });
- describe('and on the rules management tab', () => {
- beforeEach(() => {
- login();
visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
- });
-
- it('persists after reloading the page', () => {
- changeRulesTableState();
- goToTablePage(2);
-
- cy.reload();
expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(2);
+ expectFilterSearchTerm('test');
+ expectFilterByTags(['tag-a']);
+ expectFilterByPrebuiltRules();
+ expectFilterByEnabledRules();
+ expectTableSorting('Severity', 'desc');
});
- it('persists after navigating back from a rule details page', () => {
- changeRulesTableState();
- goToTablePage(2);
-
- goToRuleDetailsOf('rule 6');
- cy.go('back');
+ it('prefers url state over storage state', () => {
+ setStorageState({
+ searchTerm: 'test',
+ tags: ['tag-c'],
+ source: 'prebuilt',
+ enabled: true,
+ field: 'severity',
+ order: 'desc',
+ perPage: 10,
+ });
+
+ visitRulesTableWithState({
+ searchTerm: 'rule',
+ tags: ['tag-b'],
+ source: 'custom',
+ enabled: false,
+ field: 'name',
+ order: 'asc',
+ perPage: 5,
+ page: 2,
+ });
expectRulesManagementTab();
expectRulesTableState();
expectTablePage(2);
});
- it('persists after navigating to another page inside Security Solution', () => {
- changeRulesTableState();
- goToTablePage(2);
+ describe('and on the rules management tab', () => {
+ beforeEach(() => {
+ login();
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ });
- visit(DASHBOARDS_URL);
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ it('persists after reloading the page', () => {
+ changeRulesTableState();
+ goToTablePage(2);
- expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(1);
- });
+ cy.reload();
- it('persists after navigating to another page outside Security Solution', () => {
- changeRulesTableState();
- goToTablePage(2);
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(2);
+ });
- visit(KIBANA_HOME);
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ it('persists after navigating back from a rule details page', () => {
+ changeRulesTableState();
+ goToTablePage(2);
- expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(1);
- });
- });
+ goToRuleDetailsOf('rule 6');
+ cy.go('back');
- describe('and on the rules monitoring tab', () => {
- beforeEach(() => {
- login();
- visit(SECURITY_DETECTIONS_RULES_MONITORING_URL);
- });
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(2);
+ });
- it('persists the selected tab', () => {
- changeRulesTableState();
+ it('persists after navigating to another page inside Security Solution', () => {
+ changeRulesTableState();
+ goToTablePage(2);
- cy.reload();
+ visit(DASHBOARDS_URL);
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
- expectRulesMonitoringTab();
- });
- });
- }
- );
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(1);
+ });
- describe('upon state format upgrade', async () => {
- beforeEach(() => {
- login();
- });
+ it('persists after navigating to another page outside Security Solution', () => {
+ changeRulesTableState();
+ goToTablePage(2);
+
+ visit(KIBANA_HOME);
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
- describe('and having state in the url', () => {
- it('ignores unsupported state key', () => {
- visitRulesTableWithState({
- someKey: 10,
- searchTerm: 'rule',
- tags: ['tag-b'],
- source: 'custom',
- enabled: false,
- field: 'name',
- order: 'asc',
- perPage: 5,
- page: 2,
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(1);
+ });
});
- expectRulesTableState();
- expectTablePage(2);
- });
- });
+ describe('and on the rules monitoring tab', () => {
+ beforeEach(() => {
+ login();
+ visit(SECURITY_DETECTIONS_RULES_MONITORING_URL);
+ });
- describe('and having state in the session storage', () => {
- it('ignores unsupported state key', () => {
- setStorageState({
- someKey: 10,
- searchTerm: 'rule',
- tags: ['tag-b'],
- source: 'custom',
- enabled: false,
- field: 'name',
- order: 'asc',
- perPage: 5,
- });
+ it('persists the selected tab', () => {
+ changeRulesTableState();
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ cy.reload();
- expectRulesTableState();
- expectTablePage(1);
- });
- });
- });
+ expectRulesMonitoringTab();
+ });
+ });
+ }
+ );
- describe('when persisted state is partially unavailable', () => {
- describe('and on the rules management tab', () => {
+ describe('upon state format upgrade', async () => {
beforeEach(() => {
login();
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
});
- it('persists after clearing the session storage', () => {
- changeRulesTableState();
- goToTablePage(2);
+ describe('and having state in the url', () => {
+ it('ignores unsupported state key', () => {
+ visitRulesTableWithState({
+ someKey: 10,
+ searchTerm: 'rule',
+ tags: ['tag-b'],
+ source: 'custom',
+ enabled: false,
+ field: 'name',
+ order: 'asc',
+ perPage: 5,
+ page: 2,
+ });
- cy.window().then((win) => {
- win.sessionStorage.clear();
+ expectRulesTableState();
+ expectTablePage(2);
});
- cy.reload();
-
- expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(2);
});
- it('persists after clearing the url state', () => {
- changeRulesTableState();
- goToTablePage(2);
+ describe('and having state in the session storage', () => {
+ it('ignores unsupported state key', () => {
+ setStorageState({
+ someKey: 10,
+ searchTerm: 'rule',
+ tags: ['tag-b'],
+ source: 'custom',
+ enabled: false,
+ field: 'name',
+ order: 'asc',
+ perPage: 5,
+ });
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
- expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(1);
+ expectRulesTableState();
+ expectTablePage(1);
+ });
});
});
- });
- describe('when corrupted', () => {
- describe('and on the rules management tab', () => {
- beforeEach(() => {
- login();
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
- });
+ describe('when persisted state is partially unavailable', () => {
+ describe('and on the rules management tab', () => {
+ beforeEach(() => {
+ login();
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ });
- it('persists after corrupting the session storage data', () => {
- changeRulesTableState();
- goToTablePage(2);
+ it('persists after clearing the session storage', () => {
+ changeRulesTableState();
+ goToTablePage(2);
- cy.window().then((win) => {
- win.sessionStorage.setItem('securitySolution.rulesTable', '!invalid');
+ cy.window().then((win) => {
+ win.sessionStorage.clear();
+ });
cy.reload();
expectRulesManagementTab();
expectRulesTableState();
expectTablePage(2);
});
- });
- it('persists after corrupting the url param data', () => {
- changeRulesTableState();
- goToTablePage(2);
+ it('persists after clearing the url state', () => {
+ changeRulesTableState();
+ goToTablePage(2);
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, { qs: { rulesTable: '(!invalid)' } });
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
- expectRulesManagementTab();
- expectRulesTableState();
- expectTablePage(1);
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(1);
+ });
});
+ });
- it('DOES NOT persist after corrupting the session storage and url param data', () => {
- changeRulesTableState();
- goToTablePage(2);
+ describe('when corrupted', () => {
+ describe('and on the rules management tab', () => {
+ beforeEach(() => {
+ login();
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL);
+ });
- visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, {
- qs: { rulesTable: '(!invalid)' },
- onBeforeLoad: (win) => {
+ it('persists after corrupting the session storage data', () => {
+ changeRulesTableState();
+ goToTablePage(2);
+
+ cy.window().then((win) => {
win.sessionStorage.setItem('securitySolution.rulesTable', '!invalid');
- },
+ cy.reload();
+
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(2);
+ });
+ });
+
+ it('persists after corrupting the url param data', () => {
+ changeRulesTableState();
+ goToTablePage(2);
+
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, { qs: { rulesTable: '(!invalid)' } });
+
+ expectRulesManagementTab();
+ expectRulesTableState();
+ expectTablePage(1);
});
- expectRulesManagementTab();
- expectDefaultRulesTableState();
+ it('DOES NOT persist after corrupting the session storage and url param data', () => {
+ changeRulesTableState();
+ goToTablePage(2);
+
+ visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL, {
+ qs: { rulesTable: '(!invalid)' },
+ onBeforeLoad: (win) => {
+ win.sessionStorage.setItem('securitySolution.rulesTable', '!invalid');
+ },
+ });
+
+ expectRulesManagementTab();
+ expectDefaultRulesTableState();
+ });
});
});
- });
-});
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts
index 782e70dec8379..383718c646f9d 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts
@@ -33,69 +33,74 @@ const RULE_2 = createRuleAssetSavedObject({
rule_id: 'rule_2',
});
+// TODO: https://github.com/elastic/kibana/issues/161540
// FLAKY: https://github.com/elastic/kibana/issues/165643
-describe.skip('Rules table: selection', { tags: ['@ess', '@serverless'] }, () => {
- before(() => {
- cleanKibana();
- });
+describe.skip(
+ 'Rules table: selection',
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
+ () => {
+ before(() => {
+ cleanKibana();
+ });
- beforeEach(() => {
- login();
- /* Create and install two mock rules */
- createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] });
- visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
- waitForPrebuiltDetectionRulesToBeLoaded();
- });
+ beforeEach(() => {
+ login();
+ /* Create and install two mock rules */
+ createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] });
+ visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPrebuiltDetectionRulesToBeLoaded();
+ });
- it('should correctly update the selection label when rules are individually selected and unselected', () => {
- waitForPrebuiltDetectionRulesToBeLoaded();
+ it('should correctly update the selection label when rules are individually selected and unselected', () => {
+ waitForPrebuiltDetectionRulesToBeLoaded();
- selectRulesByName(['Test rule 1', 'Test rule 2']);
+ selectRulesByName(['Test rule 1', 'Test rule 2']);
- cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '2');
+ cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '2');
- unselectRulesByName(['Test rule 1', 'Test rule 2']);
+ unselectRulesByName(['Test rule 1', 'Test rule 2']);
- cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0');
- });
+ cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0');
+ });
- it('should correctly update the selection label when rules are bulk selected and then bulk un-selected', () => {
- waitForPrebuiltDetectionRulesToBeLoaded();
+ it('should correctly update the selection label when rules are bulk selected and then bulk un-selected', () => {
+ waitForPrebuiltDetectionRulesToBeLoaded();
- cy.get(SELECT_ALL_RULES_BTN).click();
+ cy.get(SELECT_ALL_RULES_BTN).click();
- getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
- cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount);
- });
+ getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
+ cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount);
+ });
- // Un-select all rules via the Bulk Selection button from the Utility bar
- cy.get(SELECT_ALL_RULES_BTN).click();
+ // Un-select all rules via the Bulk Selection button from the Utility bar
+ cy.get(SELECT_ALL_RULES_BTN).click();
- // Current selection should be 0 rules
- cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0');
- // Bulk selection button should be back to displaying all rules
- getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
- cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount);
+ // Current selection should be 0 rules
+ cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0');
+ // Bulk selection button should be back to displaying all rules
+ getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
+ cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount);
+ });
});
- });
- it('should correctly update the selection label when rules are bulk selected and then unselected via the table select all checkbox', () => {
- waitForPrebuiltDetectionRulesToBeLoaded();
+ it('should correctly update the selection label when rules are bulk selected and then unselected via the table select all checkbox', () => {
+ waitForPrebuiltDetectionRulesToBeLoaded();
- cy.get(SELECT_ALL_RULES_BTN).click();
+ cy.get(SELECT_ALL_RULES_BTN).click();
- getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
- cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount);
- });
+ getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
+ cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount);
+ });
- // Un-select all rules via the Un-select All checkbox from the table
- cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
+ // Un-select all rules via the Un-select All checkbox from the table
+ cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
- // Current selection should be 0 rules
- cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0');
- // Bulk selection button should be back to displaying all rules
- getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
- cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount);
+ // Current selection should be 0 rules
+ cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0');
+ // Bulk selection button should be back to displaying all rules
+ getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => {
+ cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount);
+ });
});
- });
-});
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts
index d61d6fca98958..f132f89b248a1 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_sorting.cy.ts
@@ -36,6 +36,7 @@ import {
} from '../../../../tasks/table_pagination';
import { TABLE_FIRST_PAGE, TABLE_SECOND_PAGE } from '../../../../screens/table_pagination';
+// TODO: https://github.com/elastic/kibana/issues/161540
describe('Rules table: sorting', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
before(() => {
cleanKibana();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts
index 552f1c4f109e9..7a2c6aeb35a22 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists/value_lists.cy.ts
@@ -35,8 +35,10 @@ const TEXT_LIST_FILE_NAME = 'value_list.txt';
const IPS_LIST_FILE_NAME = 'ip_list.txt';
const CIDRS_LIST_FILE_NAME = 'cidr_list.txt';
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165699
-describe('value lists', () => {
+describe('value lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
+ // TODO: https://github.com/elastic/kibana/issues/161539
describe('management modal', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
beforeEach(() => {
login();
@@ -56,6 +58,7 @@ describe('value lists', () => {
closeValueListsModal();
});
+ // TODO: https://github.com/elastic/kibana/issues/161539
// Flaky in serverless tests
describe('create list types', { tags: ['@brokenInServerless'] }, () => {
beforeEach(() => {
@@ -115,6 +118,7 @@ describe('value lists', () => {
});
});
+ // TODO: https://github.com/elastic/kibana/issues/161539
// Flaky in serverless tests
describe('delete list types', { tags: ['@brokenInServerless'] }, () => {
it('deletes a "keyword" list from an uploaded file', () => {
@@ -162,6 +166,7 @@ describe('value lists', () => {
});
});
+ // TODO: https://github.com/elastic/kibana/issues/161539
// Flaky in serverless tests
describe('export list types', { tags: ['@brokenInServerless'] }, () => {
it('exports a "keyword" list from an uploaded file', () => {
@@ -259,11 +264,17 @@ describe('value lists', () => {
});
});
- describe('user with restricted access role', { tags: '@ess' }, () => {
- it('Does not allow a t1 analyst user to upload a value list', () => {
- login(ROLES.t1_analyst);
- visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL, ROLES.t1_analyst);
- cy.get(VALUE_LISTS_MODAL_ACTIVATOR).should('have.attr', 'disabled');
- });
- });
+ // TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless
+ // TODO: https://github.com/elastic/kibana/issues/161539
+ describe(
+ 'user with restricted access role',
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
+ () => {
+ it('Does not allow a t1 analyst user to upload a value list', () => {
+ login(ROLES.t1_analyst);
+ visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL, ROLES.t1_analyst);
+ cy.get(VALUE_LISTS_MODAL_ACTIVATOR).should('have.attr', 'disabled');
+ });
+ }
+ );
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts
index 36bc18210863f..fca7699af7843 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts
@@ -39,13 +39,14 @@ import {
previewErrorButtonClick,
} from '../../tasks/entity_analytics';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Entity analytics management page',
{
env: {
ftrConfig: { enableExperimental: ['riskScoringRoutesEnabled', 'riskScoringPersistence'] },
},
- tags: ['@ess', '@brokenInServerless'],
+ tags: ['@ess', '@serverless', '@brokenInServerless'],
},
() => {
before(() => {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts
index 3ca8b633e6564..184152c8c5bfd 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts
@@ -36,10 +36,11 @@ import {
} from '../../../screens/exceptions';
import { goToEndpointExceptionsTab, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details';
+// TODO: https://github.com/elastic/kibana/issues/161539
// See https://github.com/elastic/kibana/issues/163967
describe.skip(
'Endpoint Exceptions workflows from Alert',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
const ITEM_NAME = 'Sample Exception List Item';
const ITEM_NAME_EDIT = 'Sample Exception List Item';
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts
index afd1f6c36e29b..41a4dd607ffe6 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts
@@ -37,10 +37,11 @@ import {
} from '../../../../screens/exceptions';
import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule';
+// TODO: https://github.com/elastic/kibana/issues/161539
// See https://github.com/elastic/kibana/issues/163967
describe.skip(
'Auto populate exception with Alert data',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
const ITEM_NAME = 'Sample Exception Item';
const ITEM_NAME_EDIT = 'Sample Exception Item Edit';
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts
index f85e572160796..6efdc2e8c515b 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts
@@ -26,8 +26,9 @@ import {
submitNewExceptionItem,
} from '../../../../tasks/exceptions';
+// TODO: https://github.com/elastic/kibana/issues/161539
// See https://github.com/elastic/kibana/issues/163967
-describe('Close matching Alerts ', () => {
+describe('Close matching Alerts ', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
const ITEM_NAME = 'Sample Exception Item';
beforeEach(() => {
@@ -53,7 +54,8 @@ describe('Close matching Alerts ', () => {
cy.task('esArchiverUnload', 'exceptions');
});
- it('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => {
+ // TODO: https://github.com/elastic/kibana/issues/161539
+ it.skip('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => {
cy.get(LOADING_INDICATOR).should('not.exist');
addExceptionFromFirstAlert();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts
index 04ef2368a7e1c..97df23c521181 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/flyout_validation.cy.ts
@@ -56,6 +56,7 @@ import {
} from '../../../tasks/api_calls/exceptions';
import { getExceptionList } from '../../../objects/exception';
+// TODO: https://github.com/elastic/kibana/issues/161539
// Test Skipped until we fix the Flyout rerendering issue
// https://github.com/elastic/kibana/issues/154994
@@ -64,7 +65,7 @@ import { getExceptionList } from '../../../objects/exception';
// to test in enzyme and very small changes can inadvertently add
// bugs. As the complexity within the builder grows, these should
// ensure the most basic logic holds.
-describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless'] }, () => {
+describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
before(() => {
cy.task('esArchiverResetKibana');
// this is a made-up index that has just the necessary
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts
index 2cf577d41e167..8da64aebc92d9 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/multiple_conditions.cy.ts
@@ -25,12 +25,13 @@ import {
import { ruleDetailsUrl } from '../../../urls/navigation';
import { deleteAlertsAndRules } from '../../../tasks/common';
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165651
// FLAKY: https://github.com/elastic/kibana/issues/165734
// FLAKY: https://github.com/elastic/kibana/issues/165652
describe(
'Add multiple conditions and validate the generated exceptions',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
beforeEach(() => {
cy.task('esArchiverResetKibana');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts
index 0969b53d8cee6..ff571faa01468 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/entry/use_value_list.cy.ts
@@ -43,10 +43,11 @@ const goToRulesAndOpenValueListModal = () => {
openValueListsModal();
};
+// TODO: https://github.com/elastic/kibana/issues/161539
// Flaky on serverless
describe(
'Use Value list in exception entry',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
beforeEach(() => {
cleanKibana();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts
index 170530f8c8045..c5a1a8e25b337 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts
@@ -46,10 +46,11 @@ import {
createEndpointExceptionListItem,
} from '../../../tasks/api_calls/exceptions';
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165736
describe(
'Add endpoint exception from rule details',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
const ITEM_NAME = 'Sample Exception List Item';
const NEW_ITEM_NAME = 'Exception item-EDITED';
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts
index 11f5a58679404..2f3d10a0b3cee 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts
@@ -58,273 +58,281 @@ import {
} from '../../../tasks/api_calls/exceptions';
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
-describe('Add/edit exception from rule details', { tags: ['@ess', '@brokenInServerless'] }, () => {
- const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
- const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
- const ITEM_FIELD = 'unique_value.test';
-
- before(() => {
- cy.task('esArchiverResetKibana');
- cy.task('esArchiverLoad', { archiveName: 'exceptions' });
- });
-
- after(() => {
- cy.task('esArchiverUnload', 'exceptions');
- });
-
- beforeEach(() => {
- login();
- deleteAlertsAndRules();
-
- const exceptionList = getExceptionList();
- deleteExceptionList(exceptionList.list_id, exceptionList.namespace_type);
- });
-
- describe('existing list and items', () => {
- const exceptionList = getExceptionList();
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe(
+ 'Add/edit exception from rule details',
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ () => {
+ const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
+ const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
+ const ITEM_FIELD = 'unique_value.test';
+
+ before(() => {
+ cy.task('esArchiverResetKibana');
+ cy.task('esArchiverLoad', { archiveName: 'exceptions' });
+ });
+
+ after(() => {
+ cy.task('esArchiverUnload', 'exceptions');
+ });
+
beforeEach(() => {
- // create rule with exceptions
- createExceptionList(exceptionList, exceptionList.list_id).then((response) => {
- createExceptionListItem(exceptionList.list_id, {
- list_id: exceptionList.list_id,
- item_id: 'simple_list_item',
- tags: [],
- type: 'simple',
- description: 'Test exception item 2',
- name: 'Sample Exception List Item 2',
- namespace_type: 'single',
- entries: [
- {
- field: ITEM_FIELD,
- operator: 'included',
- type: 'match_any',
- value: ['foo'],
- },
- ],
- });
+ login();
+ deleteAlertsAndRules();
- createRule(
- getNewRule({
- query: 'agent.name:*',
- index: ['exceptions*'],
- exceptions_list: [
+ const exceptionList = getExceptionList();
+ deleteExceptionList(exceptionList.list_id, exceptionList.namespace_type);
+ });
+
+ describe('existing list and items', () => {
+ const exceptionList = getExceptionList();
+ beforeEach(() => {
+ // create rule with exceptions
+ createExceptionList(exceptionList, exceptionList.list_id).then((response) => {
+ createExceptionListItem(exceptionList.list_id, {
+ list_id: exceptionList.list_id,
+ item_id: 'simple_list_item',
+ tags: [],
+ type: 'simple',
+ description: 'Test exception item 2',
+ name: 'Sample Exception List Item 2',
+ namespace_type: 'single',
+ entries: [
{
- id: response.body.id,
- list_id: exceptionList.list_id,
- type: exceptionList.type,
- namespace_type: exceptionList.namespace_type,
+ field: ITEM_FIELD,
+ operator: 'included',
+ type: 'match_any',
+ value: ['foo'],
},
],
- rule_id: '2',
- })
- ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions')));
+ });
+
+ createRule(
+ getNewRule({
+ query: 'agent.name:*',
+ index: ['exceptions*'],
+ exceptions_list: [
+ {
+ id: response.body.id,
+ list_id: exceptionList.list_id,
+ type: exceptionList.type,
+ namespace_type: exceptionList.namespace_type,
+ },
+ ],
+ rule_id: '2',
+ })
+ ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions')));
+ });
});
- });
- it('Edits an exception item', () => {
- const NEW_ITEM_NAME = 'Exception item-EDITED';
- const ITEM_NAME = 'Sample Exception List Item 2';
+ it('Edits an exception item', () => {
+ const NEW_ITEM_NAME = 'Exception item-EDITED';
+ const ITEM_NAME = 'Sample Exception List Item 2';
- // displays existing exception items
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
- cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME);
- cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' unique_value.testis one of foo');
+ // displays existing exception items
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
+ cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME);
+ cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should(
+ 'have.text',
+ ' unique_value.testis one of foo'
+ );
- // open edit exception modal
- openEditException();
+ // open edit exception modal
+ openEditException();
- // edit exception item name
- editExceptionFlyoutItemName(NEW_ITEM_NAME);
+ // edit exception item name
+ editExceptionFlyoutItemName(NEW_ITEM_NAME);
- // check that the existing item's field is being populated
- cy.get(EXCEPTION_ITEM_CONTAINER)
- .eq(0)
- .find(FIELD_INPUT_PARENT)
- .eq(0)
- .should('have.text', ITEM_FIELD);
- cy.get(VALUES_MATCH_ANY_INPUT).should('have.text', 'foo');
+ // check that the existing item's field is being populated
+ cy.get(EXCEPTION_ITEM_CONTAINER)
+ .eq(0)
+ .find(FIELD_INPUT_PARENT)
+ .eq(0)
+ .should('have.text', ITEM_FIELD);
+ cy.get(VALUES_MATCH_ANY_INPUT).should('have.text', 'foo');
- // edit conditions
- editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
+ // edit conditions
+ editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
- // submit
- submitEditedExceptionItem();
+ // submit
+ submitEditedExceptionItem();
- // new exception item displays
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ // new exception item displays
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- // check that updates stuck
- cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME);
- cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.nameIS foo');
- });
+ // check that updates stuck
+ cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME);
+ cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.nameIS foo');
+ });
- describe('rule with existing shared exceptions', () => {
- it('Creates an exception item to add to shared list', () => {
- // displays existing exception items
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
+ describe('rule with existing shared exceptions', () => {
+ it('Creates an exception item to add to shared list', () => {
+ // displays existing exception items
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
- // open add exception modal
- addExceptionFlyoutFromViewerHeader();
+ // open add exception modal
+ addExceptionFlyoutFromViewerHeader();
- // add exception item conditions
- addExceptionConditions(getException());
+ // add exception item conditions
+ addExceptionConditions(getException());
- // Name is required so want to check that submit is still disabled
- cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
+ // Name is required so want to check that submit is still disabled
+ cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
- // add exception item name
- addExceptionFlyoutItemName('My item name');
+ // add exception item name
+ addExceptionFlyoutItemName('My item name');
- // select to add exception item to a shared list
- selectSharedListToAddExceptionTo(1);
+ // select to add exception item to a shared list
+ selectSharedListToAddExceptionTo(1);
- // not testing close alert functionality here, just ensuring that the options appear as expected
- cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
- cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled');
+ // not testing close alert functionality here, just ensuring that the options appear as expected
+ cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
+ cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled');
- // submit
- submitNewExceptionItem();
+ // submit
+ submitNewExceptionItem();
- // new exception item displays
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
- });
+ // new exception item displays
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
+ });
- it('Creates an exception item to add to rule only', () => {
- // displays existing exception items
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
+ it('Creates an exception item to add to rule only', () => {
+ // displays existing exception items
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
- // open add exception modal
- addExceptionFlyoutFromViewerHeader();
+ // open add exception modal
+ addExceptionFlyoutFromViewerHeader();
- // add exception item conditions
- addExceptionConditions(getException());
+ // add exception item conditions
+ addExceptionConditions(getException());
- // Name is required so want to check that submit is still disabled
- cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
+ // Name is required so want to check that submit is still disabled
+ cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
- // add exception item name
- addExceptionFlyoutItemName('My item name');
+ // add exception item name
+ addExceptionFlyoutItemName('My item name');
- // select to add exception item to rule only
- selectAddToRuleRadio();
+ // select to add exception item to rule only
+ selectAddToRuleRadio();
- // not testing close alert functionality here, just ensuring that the options appear as expected
- cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
- cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled');
+ // not testing close alert functionality here, just ensuring that the options appear as expected
+ cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
+ cy.get(CLOSE_ALERTS_CHECKBOX).should('not.have.attr', 'disabled');
- // submit
- submitNewExceptionItem();
+ // submit
+ submitNewExceptionItem();
- // new exception item displays
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
- });
+ // new exception item displays
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
+ });
- // Trying to figure out with EUI why the search won't trigger
- it('Can search for items', () => {
- // displays existing exception items
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
+ // Trying to figure out with EUI why the search won't trigger
+ it('Can search for items', () => {
+ // displays existing exception items
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
- // can search for an exception value
- searchForExceptionItem('foo');
+ // can search for an exception value
+ searchForExceptionItem('foo');
- // new exception item displays
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ // new exception item displays
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- // displays empty search result view if no matches found
- searchForExceptionItem('abc');
+ // displays empty search result view if no matches found
+ searchForExceptionItem('abc');
- // new exception item displays
- cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist');
+ // new exception item displays
+ cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist');
+ });
});
});
- });
- describe('rule without existing exceptions', () => {
- beforeEach(() => {
- createRule(
- getNewRule({
- query: 'agent.name:*',
- index: ['exceptions*'],
- interval: '10s',
- rule_id: 'rule_testing',
- })
- ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions')));
- });
+ describe('rule without existing exceptions', () => {
+ beforeEach(() => {
+ createRule(
+ getNewRule({
+ query: 'agent.name:*',
+ index: ['exceptions*'],
+ interval: '10s',
+ rule_id: 'rule_testing',
+ })
+ ).then((rule) => visitWithoutDateRange(ruleDetailsUrl(rule.body.id, 'rule_exceptions')));
+ });
- afterEach(() => {
- cy.task('esArchiverUnload', 'exceptions_2');
- });
+ afterEach(() => {
+ cy.task('esArchiverUnload', 'exceptions_2');
+ });
- it('Cannot create an item to add to rule but not shared list as rule has no lists attached', () => {
- // when no exceptions exist, empty component shows with action to add exception
- cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
+ it('Cannot create an item to add to rule but not shared list as rule has no lists attached', () => {
+ // when no exceptions exist, empty component shows with action to add exception
+ cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
- // open add exception modal
- openExceptionFlyoutFromEmptyViewerPrompt();
+ // open add exception modal
+ openExceptionFlyoutFromEmptyViewerPrompt();
- // add exception item conditions
- addExceptionConditions({
- field: 'agent.name',
- operator: 'is',
- values: ['foo'],
- });
+ // add exception item conditions
+ addExceptionConditions({
+ field: 'agent.name',
+ operator: 'is',
+ values: ['foo'],
+ });
- // Name is required so want to check that submit is still disabled
- cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
+ // Name is required so want to check that submit is still disabled
+ cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
- // add exception item name
- addExceptionFlyoutItemName('My item name');
+ // add exception item name
+ addExceptionFlyoutItemName('My item name');
- // select to add exception item to rule only
- selectAddToRuleRadio();
+ // select to add exception item to rule only
+ selectAddToRuleRadio();
- // Check that add to shared list is disabled, should be unless
- // rule has shared lists attached to it already
- cy.get(ADD_TO_SHARED_LIST_RADIO_INPUT).should('have.attr', 'disabled');
+ // Check that add to shared list is disabled, should be unless
+ // rule has shared lists attached to it already
+ cy.get(ADD_TO_SHARED_LIST_RADIO_INPUT).should('have.attr', 'disabled');
- // Close matching alerts
- selectBulkCloseAlerts();
+ // Close matching alerts
+ selectBulkCloseAlerts();
- // submit
- submitNewExceptionItem();
+ // submit
+ submitNewExceptionItem();
- // new exception item displays
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ // new exception item displays
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- // Alerts table should now be empty from having added exception and closed
- // matching alert
- goToAlertsTab();
- cy.get(EMPTY_ALERT_TABLE).should('exist');
+ // Alerts table should now be empty from having added exception and closed
+ // matching alert
+ goToAlertsTab();
+ cy.get(EMPTY_ALERT_TABLE).should('exist');
- // Closed alert should appear in table
- goToClosedAlertsOnRuleDetailsPage();
- cy.get(ALERTS_COUNT).should('exist');
- cy.get(ALERTS_COUNT).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`);
+ // Closed alert should appear in table
+ goToClosedAlertsOnRuleDetailsPage();
+ cy.get(ALERTS_COUNT).should('exist');
+ cy.get(ALERTS_COUNT).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`);
- // Remove the exception and load an event that would have matched that exception
- // to show that said exception now starts to show up again
- goToExceptionsTab();
+ // Remove the exception and load an event that would have matched that exception
+ // to show that said exception now starts to show up again
+ goToExceptionsTab();
- // when removing exception and again, no more exist, empty screen shows again
- removeException();
- cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
+ // when removing exception and again, no more exist, empty screen shows again
+ removeException();
+ cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
- // load more docs
- cy.task('esArchiverLoad', { archiveName: 'exceptions_2' });
+ // load more docs
+ cy.task('esArchiverLoad', { archiveName: 'exceptions_2' });
- // now that there are no more exceptions, the docs should match and populate alerts
- goToAlertsTab();
- waitForAlertsToPopulate();
- goToOpenedAlertsOnRuleDetailsPage();
- waitForTheRuleToBeExecuted();
- waitForAlertsToPopulate();
+ // now that there are no more exceptions, the docs should match and populate alerts
+ goToAlertsTab();
+ waitForAlertsToPopulate();
+ goToOpenedAlertsOnRuleDetailsPage();
+ waitForTheRuleToBeExecuted();
+ waitForAlertsToPopulate();
- cy.get(ALERTS_COUNT).should('exist');
- cy.get(ALERTS_COUNT).should('have.text', '2 alerts');
+ cy.get(ALERTS_COUNT).should('exist');
+ cy.get(ALERTS_COUNT).should('have.text', '2 alerts');
+ });
});
- });
-});
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts
index a1e3e17db597f..07fd7dd56e5c0 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts
@@ -40,9 +40,10 @@ import {
} from '../../../screens/exceptions';
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
+// TODO: https://github.com/elastic/kibana/issues/161539
describe(
'Add exception using data views from rule details',
- { tags: ['@ess', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
() => {
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
const ITEM_NAME = 'Sample Exception List Item';
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts
index d6e5637e17379..85f0a20128382 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts
@@ -26,7 +26,8 @@ import {
deleteExceptionList,
} from '../../../tasks/api_calls/exceptions';
-describe('Exceptions viewer read only', { tags: '@ess' }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539 Do we need this to run in Serverless?
+describe('Exceptions viewer read only', { tags: ['@ess', '@skipInServerless'] }, () => {
const exceptionList = getExceptionList();
beforeEach(() => {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts
index 74c47b853d95c..2c5ccd93f3cab 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/list_detail_page/list_details.cy.ts
@@ -40,10 +40,11 @@ const getExceptionList1 = () => ({
const EXCEPTION_LIST_NAME = 'Newly created list';
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165640
describe(
'Exception list detail page',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
before(() => {
cy.task('esArchiverResetKibana');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts
index c5cf8a5de9421..0c5523e42a9ee 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts
@@ -37,10 +37,11 @@ import {
waitForExceptionsTableToBeLoaded,
} from '../../../tasks/exceptions_table';
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165795
describe(
'Add, edit and delete exception',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
beforeEach(() => {
cy.task('esArchiverResetKibana');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts
index b296ff05e3b5b..f28b1fc2ed25a 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts
@@ -40,8 +40,9 @@ const getExceptionList2 = () => ({
list_id: 'exception_list_2',
});
+// TODO: https://github.com/elastic/kibana/issues/161539
// Flaky in serverless tests
-describe('Duplicate List', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
+describe('Duplicate List', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
beforeEach(() => {
cy.task('esArchiverResetKibana');
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts
index 8fc3acdb2c7ec..6f8411c2d5ada 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts
@@ -35,7 +35,9 @@ const getExceptionList2 = () => ({
name: EXCEPTION_LIST_NAME_TWO,
list_id: 'exception_list_2',
});
-describe('Filter Lists', { tags: ['@ess', '@serverless'] }, () => {
+
+// TODO: https://github.com/elastic/kibana/issues/161539
+describe('Filter Lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
beforeEach(() => {
cy.task('esArchiverResetKibana');
login();
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts
index ac10e916d762f..7d24c9009e31d 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts
@@ -20,8 +20,9 @@ import {
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
+// TODO: https://github.com/elastic/kibana/issues/161539
// Flaky in serverless
-describe('Import Lists', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
+describe('Import Lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
const LIST_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_exception_list.ndjson';
before(() => {
cy.task('esArchiverResetKibana');
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts
index 9f2b655fdfbb4..b740eb5d9d63e 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts
@@ -47,10 +47,11 @@ const getExceptionList2 = () => ({
let exceptionListResponse: Cypress.Response;
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165690
describe(
'Manage lists from "Shared Exception Lists" page',
- { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ { tags: ['@ess', '@serverless', '@skipInServerless'] },
() => {
describe('Create/Export/Delete List', () => {
before(() => {
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts
index c6ac43547fadb..d028f5a3949c1 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts
@@ -21,7 +21,8 @@ import {
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
-describe('Shared exception lists - read only', { tags: '@ess' }, () => {
+// TODO: https://github.com/elastic/kibana/issues/161539 Do we need to run it in Serverless?
+describe('Shared exception lists - read only', { tags: ['@ess', '@skipInServerless'] }, () => {
before(() => {
cy.task('esArchiverResetKibana');
});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts
index fa99225543e89..6fed0acc748c0 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/overview/cti_link_panel.cy.ts
@@ -15,8 +15,9 @@ import {
import { login, visit } from '../../tasks/login';
import { OVERVIEW_URL } from '../../urls/navigation';
+// TODO: https://github.com/elastic/kibana/issues/161539
// FLAKY: https://github.com/elastic/kibana/issues/165709
-describe.skip('CTI Link Panel', { tags: ['@ess', '@serverless'] }, () => {
+describe.skip('CTI Link Panel', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
beforeEach(() => {
login();
});
@@ -31,35 +32,40 @@ describe.skip('CTI Link Panel', { tags: ['@ess', '@serverless'] }, () => {
.and('match', /app\/integrations\/browse\/threat_intel/);
});
- describe('enabled threat intel module', { tags: ['@brokenInServerless'] }, () => {
- before(() => {
- // illegal_argument_exception: unknown setting [index.lifecycle.name]
- cy.task('esArchiverLoad', { archiveName: 'threat_indicator' });
- });
+ // TODO: https://github.com/elastic/kibana/issues/161539
+ describe(
+ 'enabled threat intel module',
+ { tags: ['@ess', '@serverless', '@brokenInServerless'] },
+ () => {
+ before(() => {
+ // illegal_argument_exception: unknown setting [index.lifecycle.name]
+ cy.task('esArchiverLoad', { archiveName: 'threat_indicator' });
+ });
- beforeEach(() => {
- login();
- });
+ beforeEach(() => {
+ login();
+ });
- after(() => {
- cy.task('esArchiverUnload', 'threat_indicator');
- });
+ after(() => {
+ cy.task('esArchiverUnload', 'threat_indicator');
+ });
- it('renders disabled dashboard module as expected when there are no events in the selected time period', () => {
- visit(
- `${OVERVIEW_URL}?sourcerer=(timerange:(from:%272021-07-08T04:00:00.000Z%27,kind:absolute,to:%272021-07-09T03:59:59.999Z%27))`
- );
- cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist');
- cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 indicators');
- });
+ it('renders disabled dashboard module as expected when there are no events in the selected time period', () => {
+ visit(
+ `${OVERVIEW_URL}?sourcerer=(timerange:(from:%272021-07-08T04:00:00.000Z%27,kind:absolute,to:%272021-07-09T03:59:59.999Z%27))`
+ );
+ cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist');
+ cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 indicators');
+ });
- it('renders dashboard module as expected when there are events in the selected time period', () => {
- visit(OVERVIEW_URL);
+ it('renders dashboard module as expected when there are events in the selected time period', () => {
+ visit(OVERVIEW_URL);
- cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist');
- cy.get(OVERVIEW_CTI_LINKS).should('not.contain.text', 'Anomali');
- cy.get(OVERVIEW_CTI_LINKS).should('contain.text', 'AbuseCH malware');
- cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 indicator');
- });
- });
+ cy.get(`${OVERVIEW_CTI_LINKS}`).should('exist');
+ cy.get(OVERVIEW_CTI_LINKS).should('not.contain.text', 'Anomali');
+ cy.get(OVERVIEW_CTI_LINKS).should('contain.text', 'AbuseCH malware');
+ cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 indicator');
+ });
+ }
+ );
});
diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json
index 140cd99b3df49..ba28de108ccf9 100644
--- a/x-pack/test/security_solution_cypress/package.json
+++ b/x-pack/test/security_solution_cypress/package.json
@@ -19,7 +19,7 @@
"junit:transform": "node ../../plugins/security_solution/scripts/junit_transformer --pathPattern '../../../target/kibana-security-solution/cypress/results/*.xml' --rootDirectory ../../../ --reportName 'Security Solution Cypress' --writeInPlace",
"cypress:serverless": "TZ=UTC node ../../plugins/security_solution/scripts/start_cypress_parallel --config-file ../../test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts --ftr-config-file ../../test/security_solution_cypress/serverless_config",
"cypress:open:serverless": "yarn cypress:serverless open --config-file ../../test/security_solution_cypress/cypress/cypress_serverless.config.ts --spec './cypress/e2e/**/*.cy.ts'",
- "cypress:run:serverless": "yarn cypress:serverless --spec '**/cypress/e2e/!(investigations|explore)/**/*.cy.ts'",
+ "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'",
"cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'",
"cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'",
"cypress:changed-specs-only:serverless": "yarn cypress:serverless --changed-specs-only --env burn=2",
diff --git a/x-pack/test_serverless/api_integration/services/index.ts b/x-pack/test_serverless/api_integration/services/index.ts
index 06e7c33fd7099..14ecca6d1f768 100644
--- a/x-pack/test_serverless/api_integration/services/index.ts
+++ b/x-pack/test_serverless/api_integration/services/index.ts
@@ -14,6 +14,7 @@ import { SvlCommonApiServiceProvider } from './svl_common_api';
import { AlertingApiProvider } from './alerting_api';
import { SamlToolsProvider } from './saml_tools';
import { DataViewApiProvider } from './data_view_api';
+import { SvlCasesServiceProvider } from './svl_cases';
export const services = {
...xpackApiIntegrationServices,
@@ -23,6 +24,7 @@ export const services = {
alertingApi: AlertingApiProvider,
samlTools: SamlToolsProvider,
dataViewApi: DataViewApiProvider,
+ svlCases: SvlCasesServiceProvider,
};
export type InheritedFtrProviderContext = GenericFtrProviderContext;
diff --git a/x-pack/test_serverless/api_integration/services/svl_cases/api.ts b/x-pack/test_serverless/api_integration/services/svl_cases/api.ts
new file mode 100644
index 0000000000000..474a92c317e9f
--- /dev/null
+++ b/x-pack/test_serverless/api_integration/services/svl_cases/api.ts
@@ -0,0 +1,233 @@
+/*
+ * 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 SuperTest from 'supertest';
+import { CASES_URL } from '@kbn/cases-plugin/common';
+import { Case, CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain';
+import type { CasePostRequest } from '@kbn/cases-plugin/common/types/api';
+import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain';
+import { CasesFindResponse } from '@kbn/cases-plugin/common/types/api';
+import { kbnTestConfig, kibanaTestSuperuserServerless } from '@kbn/test';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export function SvlCasesApiServiceProvider({ getService }: FtrProviderContext) {
+ const kbnServer = getService('kibanaServer');
+ const supertest = getService('supertest');
+
+ interface User {
+ username: string;
+ password: string;
+ description?: string;
+ roles: string[];
+ }
+
+ const superUser: User = {
+ username: 'superuser',
+ password: 'superuser',
+ roles: ['superuser'],
+ };
+
+ const defaultUser = {
+ email: null,
+ full_name: null,
+ username: kbnTestConfig.getUrlParts(kibanaTestSuperuserServerless).username,
+ };
+
+ /**
+ * A null filled user will occur when the security plugin is disabled
+ */
+ const nullUser = { email: null, full_name: null, username: null };
+
+ const findCommon = {
+ page: 1,
+ per_page: 20,
+ total: 0,
+ count_open_cases: 0,
+ count_closed_cases: 0,
+ count_in_progress_cases: 0,
+ };
+
+ const findCasesResp: CasesFindResponse = {
+ ...findCommon,
+ cases: [],
+ };
+
+ return {
+ setupAuth({
+ apiCall,
+ headers,
+ auth,
+ }: {
+ apiCall: SuperTest.Test;
+ headers: Record;
+ auth?: { user: User; space: string | null } | null;
+ }): SuperTest.Test {
+ if (!Object.hasOwn(headers, 'Cookie') && auth != null) {
+ return apiCall.auth(auth.user.username, auth.user.password);
+ }
+
+ return apiCall;
+ },
+
+ getSpaceUrlPrefix(spaceId: string | undefined | null) {
+ return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``;
+ },
+
+ async deleteAllCaseItems() {
+ await Promise.all([
+ this.deleteCasesByESQuery(),
+ this.deleteCasesUserActions(),
+ this.deleteComments(),
+ this.deleteConfiguration(),
+ this.deleteMappings(),
+ ]);
+ },
+
+ async deleteCasesUserActions(): Promise {
+ await kbnServer.savedObjects.clean({ types: ['cases-user-actions'] });
+ },
+
+ async deleteCasesByESQuery(): Promise {
+ await kbnServer.savedObjects.clean({ types: ['cases'] });
+ },
+
+ async deleteComments(): Promise {
+ await kbnServer.savedObjects.clean({ types: ['cases-comments'] });
+ },
+
+ async deleteConfiguration(): Promise {
+ await kbnServer.savedObjects.clean({ types: ['cases-configure'] });
+ },
+
+ async deleteMappings(): Promise {
+ await kbnServer.savedObjects.clean({ types: ['cases-connector-mappings'] });
+ },
+
+ /**
+ * Return a request for creating a case.
+ */
+ getPostCaseRequest(owner: string, req?: Partial): CasePostRequest {
+ return {
+ ...this.getPostCaseReq(owner),
+ ...req,
+ };
+ },
+
+ postCaseResp(owner: string, id?: string | null, req?: CasePostRequest): Partial {
+ const request = req ?? this.getPostCaseReq(owner);
+ return {
+ ...request,
+ ...(id != null ? { id } : {}),
+ comments: [],
+ duration: null,
+ severity: request.severity ?? CaseSeverity.LOW,
+ totalAlerts: 0,
+ totalComment: 0,
+ closed_by: null,
+ created_by: defaultUser,
+ external_service: null,
+ status: CaseStatuses.open,
+ updated_by: null,
+ category: null,
+ };
+ },
+
+ async createCase(
+ params: CasePostRequest,
+ expectedHttpCode: number = 200,
+ auth: { user: User; space: string | null } | null = { user: superUser, space: null },
+ headers: Record = {}
+ ): Promise {
+ const apiCall = supertest.post(`${CASES_URL}`);
+
+ this.setupAuth({ apiCall, headers, auth });
+
+ const response = await apiCall
+ .set('kbn-xsrf', 'foo')
+ .set('x-elastic-internal-origin', 'foo')
+ .set(headers)
+ .send(params)
+ .expect(expectedHttpCode);
+
+ return response.body;
+ },
+
+ async findCases({
+ query = {},
+ expectedHttpCode = 200,
+ auth = { user: superUser, space: null },
+ }: {
+ query?: Record;
+ expectedHttpCode?: number;
+ auth?: { user: User; space: string | null };
+ }): Promise {
+ const { body: res } = await supertest
+ .get(`${this.getSpaceUrlPrefix(auth.space)}${CASES_URL}/_find`)
+ .auth(auth.user.username, auth.user.password)
+ .query({ sortOrder: 'asc', ...query })
+ .set('kbn-xsrf', 'foo')
+ .set('x-elastic-internal-origin', 'foo')
+ .send()
+ .expect(expectedHttpCode);
+
+ return res;
+ },
+
+ async getCase({
+ caseId,
+ includeComments = false,
+ expectedHttpCode = 200,
+ auth = { user: superUser, space: null },
+ }: {
+ caseId: string;
+ includeComments?: boolean;
+ expectedHttpCode?: number;
+ auth?: { user: User; space: string | null };
+ }): Promise {
+ const { body: theCase } = await supertest
+ .get(
+ `${this.getSpaceUrlPrefix(
+ auth?.space
+ )}${CASES_URL}/${caseId}?includeComments=${includeComments}`
+ )
+ .set('kbn-xsrf', 'foo')
+ .set('x-elastic-internal-origin', 'foo')
+ .auth(auth.user.username, auth.user.password)
+ .expect(expectedHttpCode);
+
+ return theCase;
+ },
+
+ getFindCasesResp() {
+ return findCasesResp;
+ },
+
+ getPostCaseReq(owner: string): CasePostRequest {
+ return {
+ description: 'This is a brand new case of a bad meanie defacing data',
+ title: 'Super Bad Observability Issue',
+ tags: ['defacement'],
+ severity: CaseSeverity.LOW,
+ connector: {
+ id: 'none',
+ name: 'none',
+ type: ConnectorTypes.none,
+ fields: null,
+ },
+ settings: {
+ syncAlerts: true,
+ },
+ owner,
+ assignees: [],
+ };
+ },
+
+ getNullUser() {
+ return nullUser;
+ },
+ };
+}
diff --git a/x-pack/test_serverless/api_integration/services/svl_cases/index.ts b/x-pack/test_serverless/api_integration/services/svl_cases/index.ts
new file mode 100644
index 0000000000000..31b372e7442b3
--- /dev/null
+++ b/x-pack/test_serverless/api_integration/services/svl_cases/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+import { SvlCasesApiServiceProvider } from './api';
+import { SvlCasesOmitServiceProvider } from './omit';
+
+export function SvlCasesServiceProvider(context: FtrProviderContext) {
+ const api = SvlCasesApiServiceProvider(context);
+ const omit = SvlCasesOmitServiceProvider(context);
+
+ return {
+ api,
+ omit,
+ };
+}
diff --git a/x-pack/test_serverless/api_integration/services/svl_cases/omit.ts b/x-pack/test_serverless/api_integration/services/svl_cases/omit.ts
new file mode 100644
index 0000000000000..94ce0a479fffc
--- /dev/null
+++ b/x-pack/test_serverless/api_integration/services/svl_cases/omit.ts
@@ -0,0 +1,53 @@
+/*
+ * 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 { Case, Attachment } from '@kbn/cases-plugin/common/types/domain';
+import { omit } from 'lodash';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export function SvlCasesOmitServiceProvider({}: FtrProviderContext) {
+ interface CommonSavedObjectAttributes {
+ id?: string | null;
+ created_at?: string | null;
+ updated_at?: string | null;
+ version?: string | null;
+ [key: string]: unknown;
+ }
+
+ const savedObjectCommonAttributes = ['created_at', 'updated_at', 'version', 'id'];
+
+ return {
+ removeServerGeneratedPropertiesFromObject(
+ object: T,
+ keys: K[]
+ ): Omit {
+ return omit(object, keys);
+ },
+
+ removeServerGeneratedPropertiesFromSavedObject(
+ attributes: T,
+ keys: Array = []
+ ): Omit