From 9b73b87d5c7d639c1798e5d8572af8d0c65ffd21 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 17 Oct 2023 12:24:03 -0600 Subject: [PATCH 01/49] [Security solution] Rename connector from AWS Bedrock to Amazon Bedrock (#169025) --- .../connectors/images/bedrock-connector.png | Bin 303619 -> 297037 bytes .../connectors/images/bedrock-params.png | Bin 197882 -> 194017 bytes .../action_type_selector_modal.tsx | 8 ++++++++ .../content/prompts/welcome/translations.ts | 2 +- .../server/routes/evaluate/utils.ts | 2 +- .../common/bedrock/constants.ts | 2 +- .../connector_types/bedrock/bedrock.test.tsx | 4 ++-- .../connector_types/bedrock/bedrock.tsx | 2 +- .../connector_types/bedrock/constants.tsx | 2 +- .../connector_types/bedrock/translations.ts | 2 +- .../connector_types/bedrock/index.test.ts | 8 ++++---- .../server/connector_types/bedrock/index.ts | 2 +- .../stack_connectors/server/plugin.test.ts | 2 +- .../tests/actions/connector_types/bedrock.ts | 4 ++-- 14 files changed, 24 insertions(+), 16 deletions(-) diff --git a/docs/management/connectors/images/bedrock-connector.png b/docs/management/connectors/images/bedrock-connector.png index 22a537183171de4e7aa029369923f4d11529c716..cfdb19f3fc6c2fdebcd771b69991ce13d17cf48f 100644 GIT binary patch literal 297037 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%0D&UW&3?34V_H z*<#NY7_Rhp-ci(*CX-oud-pw}TnfdE(_5>R(U3oqqvXw{fnJX&q#dn6-r9Ku6s+fb zN=&lY5{{RgY0-zIZy$|>5E$N_NRE|III6Q{gE(fP+3MJ@ndv_L)|QyJ9{7}HJL4C7 zc%ItG*erJ)0)*2N^P#=$NJ-`hiI2T(XtL_xe_4Ib3w%!WweoUcsg5natgPxru;>YW z)>4>3<=`wLtOt3!Uu-pN5YjyXRD;#-h<|OWh4BE#3ok6Qo4Ze|X&E+{fEoCVm9egK z4{+|1Wcy|62o`ME_!@q%5a(w#;Bii=l{GfI+Fc-uPXZemQQ^D5fkcW9`LZb6mmFEN zl%WDB#tL}GTPZ2d@VWS-r=FSjlL zh-r+5{=2UwM|!HeE`u3`jEEc9HSRCK9Y|T59Yp|&2rGq|e?!UI>yZ8xJWX4G;PPRu zG-~WIBV#e7v}0k)^Vb$w@8?^72?^<$!9&Tf9p~F?eTnV6Tb-tiF<3LT$U3Oc6^ba5 zvjk{h09drsmjop{^6wB&r~Q|4dVi}zhQB$ZqEx1$ZYqsr%X?kTqS_QyRowGJuu6z} zvQrzVV!??K@Gw+*-?p+*iUd{i%PqGJ@7<^?&jwWOvWy;o^EgO&-MFmyGe2ZqHF%28 zd$F^0<7UIs2CX^@{YN|Z zw^XXcPe2$<=w?%qg7?e=R?(XWhM}%tf$;(tOC-OlS64sWx#G`%IWeNM@UOQwpIYJj zV@E~pxm(vW{>{(2|Mh4~lfpssq6NixYv}{Q3qYii^>-){lfFTB(AbRm|xzz!S#&YgT`Q|P0nv`8wBsWIM2&x!mj zKBYAj4EF>uK(Ez^1<>;Oc7}uN#9TSYufMxwt49G z^~x9LKNwbYo=tK6gI_lYmWIggGNjBsok1(eps|GU#ew15hp(uAicwZj%iH#er{kbh z&8$gBeDeC%7udp;kD_?zD>N`PBuAFt1P|!=3|NL2v_L+minM(ZP0|rmN=2_ks`WRb zW?-b<^z*J4GY%-0ob(XSKn*N3kYj8&KVmao`7M;FSrLTM1o~{nENDbluT&GcTyL8` zFtLmfF|VVaYSe-}3Whc;gyiU85Z>R2T!Pm*`m7nBSVf5WjGMEvpVBMIx& z{izy`;!8#qaGO@$_14w0aW?hYCNp6&_Rbkp!ue91byT(?);v$ki%s*E`aT6MYplNf zsx5MRWT$ZmWxWoacy+wcy5-dGB*-_^^fcfOo=ZUDun@wWL;rIUSc*4ufo^y2V+A?z zZGjfISdS?7R6n8PQfneYFF#C^?|Sf7>w*>HXX}ZE$-zHCk`5tD3I6HjoycP=K5m9J z{a-RxC+p(m%%hi<>y~i0%dR4(gF(52_T(>XFZ&f%qiHND>I+WW*tnR~s vo>Kd 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%0Drf;@B+K5?PuH56 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

*%;niR&;=Y;u=&q=jm^~(C&BibpgnzeNW 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�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

^-ArpzGu!EB&0z5)z1p-NBnF#ktWucI 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

O=%=5m`X^dVuE%EaJ)A%-K+aSk6CU}wyrkA968 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 literal 197882 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 z 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 T?#YS)|IV3M{aSYB+He00+65E% diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx index 84a72e0cd2b5f..6184525877d1c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { ActionType } from '@kbn/actions-plugin/common'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import { css } from '@emotion/css/dist/emotion-css.cjs'; import * as i18n from '../translations'; interface Props { @@ -26,6 +27,12 @@ interface Props { onClose: () => void; onSelect: (actionType: ActionType) => void; } +const itemClassName = css` + .euiKeyPadMenuItem__label { + white-space: nowrap; + overflow: hidden; + } +`; export const ActionTypeSelectorModal = ({ actionTypes, @@ -46,6 +53,7 @@ export const ActionTypeSelectorModal = ({ return ( { describe('actionTypeRegistry.get() works', () => { test('connector type static data is as expected', () => { expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.selectMessage).toBe('Send a request to AWS Bedrock.'); - expect(actionTypeModel.actionTypeTitle).toBe('AWS Bedrock'); + expect(actionTypeModel.selectMessage).toBe('Send a request to Amazon Bedrock.'); + expect(actionTypeModel.actionTypeTitle).toBe('Amazon Bedrock'); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx index e6d674354511a..361caed6882c2 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/bedrock.tsx @@ -21,7 +21,7 @@ export function getConnectorType(): BedrockConnector { id: BEDROCK_CONNECTOR_ID, iconClass: lazy(() => import('./logo')), selectMessage: i18n.translate('xpack.stackConnectors.components.bedrock.selectMessageText', { - defaultMessage: 'Send a request to AWS Bedrock.', + defaultMessage: 'Send a request to Amazon Bedrock.', }), actionTypeTitle: BEDROCK_TITLE, validateParams: async ( diff --git a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/constants.tsx b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/constants.tsx index 7ee3e35cecf15..88fc42e004bf8 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/bedrock/constants.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/bedrock/constants.tsx @@ -33,7 +33,7 @@ export const bedrockConfig: ConfigFieldSchema[] = [ defaultValue: DEFAULT_BEDROCK_URL, helpText: ( { configurationUtilities = actionsConfigMock.create(); connectorType = getConnectorType(); }); - test('exposes the connector as `AWS Bedrock` with id `.bedrock`', () => { + test('exposes the connector as `Amazon Bedrock` with id `.bedrock`', () => { expect(connectorType.id).toEqual('.bedrock'); - expect(connectorType.name).toEqual('AWS Bedrock'); + expect(connectorType.name).toEqual('Amazon Bedrock'); }); describe('config validation', () => { test('config validation passes when only required fields are provided', () => { @@ -55,7 +55,7 @@ describe('Bedrock Connector', () => { expect(() => { configValidator(config, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( - '"Error configuring AWS Bedrock action: Error: URL Error: Invalid URL: example.com/do-something"' + '"Error configuring Amazon Bedrock action: Error: URL Error: Invalid URL: example.com/do-something"' ); }); @@ -75,7 +75,7 @@ describe('Bedrock Connector', () => { expect(() => { configValidator(config, { configurationUtilities: configUtils }); }).toThrowErrorMatchingInlineSnapshot( - `"Error configuring AWS Bedrock action: Error: error validating url: target url is not present in allowedHosts"` + `"Error configuring Amazon Bedrock action: Error: error validating url: target url is not present in allowedHosts"` ); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts index 11050628203a9..02b2bff9a93ae 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts @@ -43,7 +43,7 @@ export const configValidator = (configObject: Config, validatorServices: Validat } catch (err) { throw new Error( i18n.translate('xpack.stackConnectors.bedrock.configurationErrorApiProvider', { - defaultMessage: 'Error configuring AWS Bedrock action: {err}', + defaultMessage: 'Error configuring Amazon Bedrock action: {err}', values: { err, }, diff --git a/x-pack/plugins/stack_connectors/server/plugin.test.ts b/x-pack/plugins/stack_connectors/server/plugin.test.ts index c728ee92cea40..c5c16a29647fd 100644 --- a/x-pack/plugins/stack_connectors/server/plugin.test.ts +++ b/x-pack/plugins/stack_connectors/server/plugin.test.ts @@ -164,7 +164,7 @@ describe('Stack Connectors Plugin', () => { 4, expect.objectContaining({ id: '.bedrock', - name: 'AWS Bedrock', + name: 'Amazon Bedrock', }) ); expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith( diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts index 8d4412cf37a19..4983d19d36b69 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/bedrock.ts @@ -160,7 +160,7 @@ export default function bedrockTest({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'error validating action type config: Error configuring AWS Bedrock action: Error: error validating url: target url "http://bedrock.mynonexistent.com" is not added to the Kibana config xpack.actions.allowedHosts', + 'error validating action type config: Error configuring Amazon Bedrock action: Error: error validating url: target url "http://bedrock.mynonexistent.com" is not added to the Kibana config xpack.actions.allowedHosts', }); }); }); @@ -280,7 +280,7 @@ export default function bedrockTest({ getService }: FtrProviderContext) { status: 'error', retry: true, message: 'an error occurred while running the action', - service_message: `Sub action "invalidAction" is not registered. Connector id: ${bedrockActionId}. Connector name: AWS Bedrock. Connector type: .bedrock`, + service_message: `Sub action "invalidAction" is not registered. Connector id: ${bedrockActionId}. Connector name: Amazon Bedrock. Connector type: .bedrock`, }); }); }); From 0554daacb8e50f44580047701312d5bb14d2c7b4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 17 Oct 2023 13:23:43 -0500 Subject: [PATCH 02/49] skip failing test suite (#169133) --- .../pages/policy/view/integration_tests/policy_list.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/integration_tests/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/integration_tests/policy_list.test.tsx index ee520a98632bc..abb68f1d79814 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/integration_tests/policy_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/integration_tests/policy_list.test.tsx @@ -25,7 +25,8 @@ jest.mock('../../../../../common/components/user_privileges'); const getPackagePolicies = sendGetEndpointSpecificPackagePolicies as jest.Mock; const useUserPrivilegesMock = useUserPrivileges as jest.Mock; -describe('When on the policy list page', () => { +// Failing: See https://github.com/elastic/kibana/issues/169133 +describe.skip('When on the policy list page', () => { let render: () => ReturnType; let renderResult: ReturnType; let history: AppContextTestRender['history']; From 843aaf2f04dd12d4599413c34a9c78d089d54224 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 17 Oct 2023 20:04:20 +0100 Subject: [PATCH 03/49] [SecuritySolution][DataQualityDashboard] Migrate data quality dashboard APIs to versioned router (#169037) ## Summary https://github.com/elastic/kibana/issues/168334 https://github.com/elastic/kibana/issues/166271 Before: Screenshot 2023-10-16 at 21 45 02 Screenshot 2023-10-16 at 21 45 09 After: Screenshot 2023-10-16 at 22 41 31 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../impl/data_quality/helpers.ts | 3 +- .../mock_auditbeat_pattern_rollup.ts | 6 + .../mock_packetbeat_pattern_rollup.ts | 2 + .../data_quality/use_ilm_explain/index.tsx | 2 + .../impl/data_quality/use_mappings/helpers.ts | 2 + .../impl/data_quality/use_stats/index.tsx | 2 + .../use_unallowed_values/helpers.test.ts | 2 + .../use_unallowed_values/helpers.ts | 2 + .../common/constants.ts | 1 + .../server/__mocks__/server.ts | 75 +++++++--- .../server/plugin.ts | 8 +- .../server/routes/get_ilm_explain.test.ts | 10 +- .../server/routes/get_ilm_explain.ts | 58 ++++---- .../server/routes/get_index_mappings.test.ts | 13 +- .../server/routes/get_index_mappings.ts | 54 +++---- .../server/routes/get_index_stats.test.ts | 11 +- .../server/routes/get_index_stats.ts | 133 ++++++++++-------- .../routes/get_unallowed_field_values.test.ts | 10 +- .../routes/get_unallowed_field_values.ts | 54 +++---- .../server/schemas/common.ts | 2 +- .../ecs_data_quality_dashboard/tsconfig.json | 2 + 21 files changed, 285 insertions(+), 167 deletions(-) diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts index 2ebfec9a7f257..ba195f0de0e15 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts @@ -27,6 +27,7 @@ import type { } from './types'; const EMPTY_INDEX_NAMES: string[] = []; +export const INTERNAL_API_VERSION = '1'; export const getIndexNames = ({ ilmExplain, @@ -282,7 +283,7 @@ export const getSizeInBytes = ({ }: { indexName: string; stats: Record | null; -}): number => (stats && stats[indexName]?.primaries?.store?.size_in_bytes) ?? 0; +}): number => (stats && stats[indexName]?.primaries?.store?.total_data_set_size_in_bytes) ?? 0; export const getTotalDocsCount = ({ indexNames, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts index 7c18523e44aa3..2f83f899dc0d2 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts @@ -53,6 +53,7 @@ export const auditbeatNoResults: PatternRollup = { primaries: { store: { size_in_bytes: 18791790, + total_data_set_size_in_bytes: 18791790, reserved_in_bytes: 0, }, }, @@ -70,6 +71,7 @@ export const auditbeatNoResults: PatternRollup = { primaries: { store: { size_in_bytes: 247, + total_data_set_size_in_bytes: 247, reserved_in_bytes: 0, }, }, @@ -87,6 +89,7 @@ export const auditbeatNoResults: PatternRollup = { primaries: { store: { size_in_bytes: 28409, + total_data_set_size_in_bytes: 28409, reserved_in_bytes: 0, }, }, @@ -182,6 +185,7 @@ export const auditbeatWithAllResults: PatternRollup = { primaries: { store: { size_in_bytes: 18791790, + total_data_set_size_in_bytes: 18791790, reserved_in_bytes: 0, }, }, @@ -199,6 +203,7 @@ export const auditbeatWithAllResults: PatternRollup = { primaries: { store: { size_in_bytes: 247, + total_data_set_size_in_bytes: 247, reserved_in_bytes: 0, }, }, @@ -216,6 +221,7 @@ export const auditbeatWithAllResults: PatternRollup = { primaries: { store: { size_in_bytes: 28409, + total_data_set_size_in_bytes: 28409, reserved_in_bytes: 0, }, }, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts index b04c8bb87600a..369803a44a3dd 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts @@ -51,6 +51,7 @@ export const packetbeatNoResults: PatternRollup = { primaries: { store: { size_in_bytes: 512194751, + total_data_set_size_in_bytes: 512194751, reserved_in_bytes: 0, }, }, @@ -68,6 +69,7 @@ export const packetbeatNoResults: PatternRollup = { primaries: { store: { size_in_bytes: 584326147, + total_data_set_size_in_bytes: 584326147, reserved_in_bytes: 0, }, }, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx index ae643745bd805..4e95549338874 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx @@ -9,6 +9,7 @@ import type { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch import { useEffect, useState } from 'react'; import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; +import { INTERNAL_API_VERSION } from '../helpers'; import * as i18n from '../translations'; const ILM_EXPLAIN_ENDPOINT = '/internal/ecs_data_quality_dashboard/ilm_explain'; @@ -43,6 +44,7 @@ export const useIlmExplain = (pattern: string): UseIlmExplain => { { method: 'GET', signal: abortController.signal, + version: INTERNAL_API_VERSION, } ); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts index 8aab64729df2f..809f543c0c0ae 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts @@ -9,6 +9,7 @@ import type { HttpHandler } from '@kbn/core-http-browser'; import type { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; import * as i18n from '../translations'; +import { INTERNAL_API_VERSION } from '../helpers'; export const MAPPINGS_API_ROUTE = '/internal/ecs_data_quality_dashboard/mappings'; @@ -29,6 +30,7 @@ export async function fetchMappings({ { method: 'GET', signal: abortController.signal, + version: INTERNAL_API_VERSION, } ); } catch (e) { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx index 6875dad3d4dfc..fce940de15f75 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx @@ -11,6 +11,7 @@ import { HttpFetchQuery } from '@kbn/core/public'; import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; import * as i18n from '../translations'; +import { INTERNAL_API_VERSION } from '../helpers'; const STATS_ENDPOINT = '/internal/ecs_data_quality_dashboard/stats'; @@ -53,6 +54,7 @@ export const useStats = ({ const response = await httpFetch>( `${STATS_ENDPOINT}/${encodedIndexName}`, { + version: INTERNAL_API_VERSION, method: 'GET', signal: abortController.signal, query, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts index 7b0a77d9af564..cad285a4bc976 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts @@ -15,6 +15,7 @@ import { } from './helpers'; import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; import { UnallowedValueRequestItem, UnallowedValueSearchResult } from '../types'; +import { INTERNAL_API_VERSION } from '../helpers'; describe('helpers', () => { let originalFetch: typeof global['fetch']; @@ -406,6 +407,7 @@ describe('helpers', () => { headers: { 'Content-Type': 'application/json' }, method: 'POST', signal: abortController.signal, + version: INTERNAL_API_VERSION, } ); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts index 5a331e7e1b8da..a193456d4afa9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts @@ -6,6 +6,7 @@ */ import type { HttpHandler } from '@kbn/core-http-browser'; +import { INTERNAL_API_VERSION } from '../helpers'; import * as i18n from '../translations'; import type { Bucket, @@ -81,6 +82,7 @@ export async function fetchUnallowedValues({ headers: { 'Content-Type': 'application/json' }, method: 'POST', signal: abortController.signal, + version: INTERNAL_API_VERSION, }); } catch (e) { throw new Error( diff --git a/x-pack/plugins/ecs_data_quality_dashboard/common/constants.ts b/x-pack/plugins/ecs_data_quality_dashboard/common/constants.ts index 51455c071b519..52c734797f726 100755 --- a/x-pack/plugins/ecs_data_quality_dashboard/common/constants.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/common/constants.ts @@ -13,3 +13,4 @@ export const GET_INDEX_STATS = `${BASE_PATH}/stats/{pattern}`; export const GET_INDEX_MAPPINGS = `${BASE_PATH}/mappings/{pattern}`; export const GET_UNALLOWED_FIELD_VALUES = `${BASE_PATH}/unallowed_field_values`; export const GET_ILM_EXPLAIN = `${BASE_PATH}/ilm_explain/{pattern}`; +export const INTERNAL_API_VERSION = '1'; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts index 7ac44e1beedf1..913c226517ce3 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts @@ -5,35 +5,73 @@ * 2.0. */ import { httpServiceMock } from '@kbn/core/server/mocks'; -import type { RequestHandler, RouteConfig, KibanaRequest } from '@kbn/core/server'; +import type { IRouter, RouteMethod, RequestHandler, KibanaRequest } from '@kbn/core/server'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; +import type { RouterMock } from '@kbn/core-http-router-server-mocks'; +import type { AddVersionOpts, VersionedRouteConfig } from '@kbn/core-http-server'; import { requestMock } from './request'; import { responseMock as responseFactoryMock } from './response'; import { requestContextMock } from './request_context'; import { responseAdapter } from './test_adapters'; +import { INTERNAL_API_VERSION } from '../../common/constants'; interface Route { - config: RouteConfig; + config: AddVersionOpts; handler: RequestHandler; } -const getRoute = (routerMock: MockServer['router']): Route => { - const routeCalls = [ - ...routerMock.get.mock.calls, - ...routerMock.post.mock.calls, - ...routerMock.put.mock.calls, - ...routerMock.patch.mock.calls, - ...routerMock.delete.mock.calls, +interface RegisteredVersionedRoute { + routeConfig: VersionedRouteConfig; + versionConfig: AddVersionOpts; + routeHandler: RequestHandler; +} + +type RouterMethod = Extract; + +export const getRegisteredVersionedRouteMock = ( + routerMock: RouterMock, + method: RouterMethod, + path: string, + version: string +): RegisteredVersionedRoute => { + const route = routerMock.versioned.getRoute(method, path); + const routeVersion = route.versions[version]; + + if (!routeVersion) { + throw new Error(`Handler for [${method}][${path}] with version [${version}] no found!`); + } + + return { + routeConfig: route.config, + versionConfig: routeVersion.config, + routeHandler: routeVersion.handler, + }; +}; + +const getRoute = (routerMock: MockServer['router'], request: KibanaRequest): Route => { + const versionedRouteCalls = [ + ...routerMock.versioned.get.mock.calls, + ...routerMock.versioned.post.mock.calls, + ...routerMock.versioned.put.mock.calls, + ...routerMock.versioned.patch.mock.calls, + ...routerMock.versioned.delete.mock.calls, ]; - const [route] = routeCalls; - if (!route) { + const [versionedRoute] = versionedRouteCalls; + + if (!versionedRoute) { throw new Error('No route registered!'); } - const [config, handler] = route; - return { config, handler }; + const { routeHandler, versionConfig } = getRegisteredVersionedRouteMock( + routerMock, + request.route.method, + request.route.path, + INTERNAL_API_VERSION + ); + + return { config: versionConfig, handler: routeHandler }; }; const buildResultMock = () => ({ ok: jest.fn((x) => x), badRequest: jest.fn((x) => x) }); @@ -53,17 +91,19 @@ class MockServer { public async inject(request: KibanaRequest, context: RequestHandlerContext = this.contextMock) { const validatedRequest = this.validateRequest(request); + const [rejection] = this.resultMock.badRequest.mock.calls; if (rejection) { throw new Error(`Request was rejected with message: '${rejection}'`); } - await this.getRoute().handler(context, validatedRequest, this.responseMock); + await this.getRoute(validatedRequest).handler(context, validatedRequest, this.responseMock); + return responseAdapter(this.responseMock); } - private getRoute(): Route { - return getRoute(this.router); + private getRoute(request: KibanaRequest): Route { + return getRoute(this.router, request); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -72,7 +112,8 @@ class MockServer { } private validateRequest(request: KibanaRequest): KibanaRequest { - const validations = this.getRoute().config.validate; + const config = this.getRoute(request).config; + const validations = config.validate && config.validate?.request; if (!validations) { return request; } diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts index d93766d2f3a7e..0c1cf336dc10d 100755 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts @@ -29,10 +29,10 @@ export class EcsDataQualityDashboardPlugin const router = core.http.createRouter(); // this would be deleted when plugin is removed // Register server side APIs - getIndexMappingsRoute(router); - getIndexStatsRoute(router); - getUnallowedFieldValuesRoute(router); - getILMExplainRoute(router); + getIndexMappingsRoute(router, this.logger); + getIndexStatsRoute(router, this.logger); + getUnallowedFieldValuesRoute(router, this.logger); + getILMExplainRoute(router, this.logger); return {}; } diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.test.ts index 737ddf781f1a9..329defab80c2b 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.test.ts @@ -12,6 +12,7 @@ import { serverMock } from '../__mocks__/server'; import { requestMock } from '../__mocks__/request'; import { requestContextMock } from '../__mocks__/request_context'; import { getILMExplainRoute } from './get_ilm_explain'; +import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; jest.mock('../lib', () => ({ fetchILMExplain: jest.fn(), @@ -20,6 +21,8 @@ jest.mock('../lib', () => ({ describe('getILMExplainRoute route', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); + let logger: MockedLogger; + const req = requestMock.create({ method: 'get', path: GET_ILM_EXPLAIN, @@ -32,9 +35,10 @@ describe('getILMExplainRoute route', () => { jest.clearAllMocks(); server = serverMock.create(); + logger = loggerMock.create(); ({ context } = requestContextMock.createTools()); - getILMExplainRoute(server.router); + getILMExplainRoute(server.router, logger); }); test('Returns index ilm information', async () => { @@ -91,11 +95,13 @@ describe('getILMExplainRoute route', () => { describe('request validation', () => { let server: ReturnType; + let logger: MockedLogger; beforeEach(() => { server = serverMock.create(); + logger = loggerMock.create(); - getILMExplainRoute(server.router); + getILMExplainRoute(server.router, logger); }); test('disallows invalid pattern', () => { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts index dab234eecaae7..c30271c62e313 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts @@ -5,41 +5,51 @@ * 2.0. */ -import { IRouter } from '@kbn/core/server'; +import { IRouter, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { GET_ILM_EXPLAIN } from '../../common/constants'; +import { GET_ILM_EXPLAIN, INTERNAL_API_VERSION } from '../../common/constants'; import { fetchILMExplain } from '../lib'; import { buildResponse } from '../lib/build_response'; import { buildRouteValidation } from '../schemas/common'; import { GetILMExplainParams } from '../schemas/get_ilm_explain'; -export const getILMExplainRoute = (router: IRouter) => { - router.get( - { +export const getILMExplainRoute = (router: IRouter, logger: Logger) => { + router.versioned + .get({ path: GET_ILM_EXPLAIN, - validate: { params: buildRouteValidation(GetILMExplainParams) }, - }, - async (context, request, response) => { - const resp = buildResponse(response); + access: 'internal', + }) + .addVersion( + { + version: INTERNAL_API_VERSION, + validate: { + request: { + params: buildRouteValidation(GetILMExplainParams), + }, + }, + }, + async (context, request, response) => { + const resp = buildResponse(response); - try { - const { client } = (await context.core).elasticsearch; - const decodedIndexName = decodeURIComponent(request.params.pattern); + try { + const { client } = (await context.core).elasticsearch; + const decodedIndexName = decodeURIComponent(request.params.pattern); - const ilmExplain = await fetchILMExplain(client, decodedIndexName); + const ilmExplain = await fetchILMExplain(client, decodedIndexName); - return response.ok({ - body: ilmExplain.indices, - }); - } catch (err) { - const error = transformError(err); + return response.ok({ + body: ilmExplain.indices, + }); + } catch (err) { + const error = transformError(err); - return resp.error({ - body: error.message, - statusCode: error.statusCode, - }); + logger.error(error.message); + return resp.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.test.ts index 6c76c84396299..34ce98d4f9378 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.test.ts @@ -12,6 +12,7 @@ import { serverMock } from '../__mocks__/server'; import { requestMock } from '../__mocks__/request'; import { requestContextMock } from '../__mocks__/request_context'; import { getIndexMappingsRoute } from './get_index_mappings'; +import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; jest.mock('../lib', () => ({ fetchMappings: jest.fn(), @@ -20,6 +21,8 @@ jest.mock('../lib', () => ({ describe('getIndexMappingsRoute route', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); + let logger: MockedLogger; + const req = requestMock.create({ method: 'get', path: GET_INDEX_MAPPINGS, @@ -32,9 +35,11 @@ describe('getIndexMappingsRoute route', () => { jest.clearAllMocks(); server = serverMock.create(); + logger = loggerMock.create(); + ({ context } = requestContextMock.createTools()); - getIndexMappingsRoute(server.router); + getIndexMappingsRoute(server.router, logger); }); test('Returns index stats', async () => { @@ -58,11 +63,11 @@ describe('getIndexMappingsRoute route', () => { describe('request validation', () => { let server: ReturnType; - + let logger: MockedLogger; beforeEach(() => { server = serverMock.create(); - - getIndexMappingsRoute(server.router); + logger = loggerMock.create(); + getIndexMappingsRoute(server.router, logger); }); test('disallows invalid pattern', () => { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts index 8b81daebeec8d..c7ab5e1d4a790 100755 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts @@ -5,41 +5,47 @@ * 2.0. */ -import { IRouter } from '@kbn/core/server'; +import { IRouter, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { fetchMappings } from '../lib'; import { buildResponse } from '../lib/build_response'; -import { GET_INDEX_MAPPINGS } from '../../common/constants'; +import { GET_INDEX_MAPPINGS, INTERNAL_API_VERSION } from '../../common/constants'; import { GetIndexMappingsParams } from '../schemas/get_index_mappings'; import { buildRouteValidation } from '../schemas/common'; -export const getIndexMappingsRoute = (router: IRouter) => { - router.get( - { +export const getIndexMappingsRoute = (router: IRouter, logger: Logger) => { + router.versioned + .get({ path: GET_INDEX_MAPPINGS, - validate: { params: buildRouteValidation(GetIndexMappingsParams) }, - }, - async (context, request, response) => { - const resp = buildResponse(response); + access: 'internal', + }) + .addVersion( + { + version: INTERNAL_API_VERSION, + validate: { request: { params: buildRouteValidation(GetIndexMappingsParams) } }, + }, + async (context, request, response) => { + const resp = buildResponse(response); - try { - const { client } = (await context.core).elasticsearch; - const decodedIndexName = decodeURIComponent(request.params.pattern); + try { + const { client } = (await context.core).elasticsearch; + const decodedIndexName = decodeURIComponent(request.params.pattern); - const mappings = await fetchMappings(client, decodedIndexName); + const mappings = await fetchMappings(client, decodedIndexName); - return response.ok({ - body: mappings, - }); - } catch (err) { - const error = transformError(err); + return response.ok({ + body: mappings, + }); + } catch (err) { + const error = transformError(err); + logger.error(error.message); - return resp.error({ - body: error.message, - statusCode: error.statusCode, - }); + return resp.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.test.ts index aa7fc306b47e8..e000809797a01 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.test.ts @@ -12,6 +12,7 @@ import { serverMock } from '../__mocks__/server'; import { requestMock } from '../__mocks__/request'; import { requestContextMock } from '../__mocks__/request_context'; import { getIndexStatsRoute } from './get_index_stats'; +import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; jest.mock('../lib', () => ({ fetchStats: jest.fn(), @@ -21,6 +22,8 @@ jest.mock('../lib', () => ({ describe('getIndexStatsRoute route', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); + let logger: MockedLogger; + const req = requestMock.create({ method: 'get', path: GET_INDEX_STATS, @@ -38,9 +41,11 @@ describe('getIndexStatsRoute route', () => { jest.clearAllMocks(); server = serverMock.create(); + logger = loggerMock.create(); + ({ context } = requestContextMock.createTools()); - getIndexStatsRoute(server.router); + getIndexStatsRoute(server.router, logger); }); test('Returns index stats', async () => { @@ -127,11 +132,13 @@ describe('getIndexStatsRoute route', () => { describe('request validation', () => { let server: ReturnType; + let logger: MockedLogger; beforeEach(() => { server = serverMock.create(); + logger = loggerMock.create(); - getIndexStatsRoute(server.router); + getIndexStatsRoute(server.router, logger); }); test('disallows invalid pattern', () => { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts index 839d21931a064..f98fa03c27523 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts @@ -5,87 +5,96 @@ * 2.0. */ import { i18n } from '@kbn/i18n'; -import { IRouter } from '@kbn/core/server'; +import { IRouter, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types'; import { fetchStats, fetchAvailableIndices } from '../lib'; import { buildResponse } from '../lib/build_response'; -import { GET_INDEX_STATS } from '../../common/constants'; +import { GET_INDEX_STATS, INTERNAL_API_VERSION } from '../../common/constants'; import { buildRouteValidation } from '../schemas/common'; import { GetIndexStatsParams, GetIndexStatsQuery } from '../schemas/get_index_stats'; -export const getIndexStatsRoute = (router: IRouter) => { - router.get( - { +export const getIndexStatsRoute = (router: IRouter, logger: Logger) => { + router.versioned + .get({ path: GET_INDEX_STATS, - validate: { - params: buildRouteValidation(GetIndexStatsParams), - query: buildRouteValidation(GetIndexStatsQuery), + access: 'internal', + }) + .addVersion( + { + version: INTERNAL_API_VERSION, + validate: { + request: { + params: buildRouteValidation(GetIndexStatsParams), + query: buildRouteValidation(GetIndexStatsQuery), + }, + }, }, - }, - async (context, request, response) => { - const resp = buildResponse(response); + async (context, request, response) => { + const resp = buildResponse(response); - try { - const { client } = (await context.core).elasticsearch; - const esClient = client.asCurrentUser; + try { + const { client } = (await context.core).elasticsearch; + const esClient = client.asCurrentUser; - const decodedIndexName = decodeURIComponent(request.params.pattern); + const decodedIndexName = decodeURIComponent(request.params.pattern); - const stats = await fetchStats(client, decodedIndexName); - const { isILMAvailable, startDate, endDate } = request.query; + const stats = await fetchStats(client, decodedIndexName); + const { isILMAvailable, startDate, endDate } = request.query; - if (isILMAvailable === true) { - return response.ok({ - body: stats.indices, - }); - } + if (isILMAvailable === true) { + return response.ok({ + body: stats.indices, + }); + } - /** - * If ILM is not available, we need to fetch the available indices with the given date range. - * `fetchAvailableIndices` returns indices that have data in the given date range. - */ - if (startDate && endDate) { - const decodedStartDate = decodeURIComponent(startDate); - const decodedEndDate = decodeURIComponent(endDate); + /** + * If ILM is not available, we need to fetch the available indices with the given date range. + * `fetchAvailableIndices` returns indices that have data in the given date range. + */ + if (startDate && endDate) { + const decodedStartDate = decodeURIComponent(startDate); + const decodedEndDate = decodeURIComponent(endDate); - const indices = await fetchAvailableIndices(esClient, { - indexPattern: decodedIndexName, - startDate: decodedStartDate, - endDate: decodedEndDate, - }); - const availableIndices = indices?.aggregations?.index?.buckets?.reduce( - (acc: Record, { key }: { key: string }) => { - if (stats.indices?.[key]) { - acc[key] = stats.indices?.[key]; - } - return acc; - }, - {} - ); + const indices = await fetchAvailableIndices(esClient, { + indexPattern: decodedIndexName, + startDate: decodedStartDate, + endDate: decodedEndDate, + }); + const availableIndices = indices?.aggregations?.index?.buckets?.reduce( + (acc: Record, { key }: { key: string }) => { + if (stats.indices?.[key]) { + acc[key] = stats.indices?.[key]; + } + return acc; + }, + {} + ); + + return response.ok({ + body: availableIndices, + }); + } else { + return resp.error({ + body: i18n.translate( + 'xpack.ecsDataQualityDashboard.getIndexStats.dateRangeRequiredErrorMessage', + { + defaultMessage: 'startDate and endDate are required', + } + ), + statusCode: 400, + }); + } + } catch (err) { + const error = transformError(err); + logger.error(error.message); - return response.ok({ - body: availableIndices, - }); - } else { return resp.error({ - body: i18n.translate( - 'xpack.ecsDataQualityDashboard.getIndexStats.dateRangeRequiredErrorMessage', - { - defaultMessage: 'startDate and endDate are required', - } - ), - statusCode: 400, + body: error.message, + statusCode: error.statusCode, }); } - } catch (err) { - const error = transformError(err); - return resp.error({ - body: error.message, - statusCode: error.statusCode, - }); } - } - ); + ); }; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.test.ts index bb59356111699..fe18893acaec1 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.test.ts @@ -12,6 +12,7 @@ import { serverMock } from '../__mocks__/server'; import { requestMock } from '../__mocks__/request'; import { requestContextMock } from '../__mocks__/request_context'; import { getUnallowedFieldValuesRoute } from './get_unallowed_field_values'; +import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; jest.mock('../lib', () => ({ getUnallowedFieldValues: jest.fn(), @@ -20,6 +21,8 @@ jest.mock('../lib', () => ({ describe('getUnallowedFieldValuesRoute route', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); + let logger: MockedLogger; + const req = requestMock.create({ method: 'post', path: GET_UNALLOWED_FIELD_VALUES, @@ -37,8 +40,9 @@ describe('getUnallowedFieldValuesRoute route', () => { server = serverMock.create(); ({ context } = requestContextMock.createTools()); + logger = loggerMock.create(); - getUnallowedFieldValuesRoute(server.router); + getUnallowedFieldValuesRoute(server.router, logger); }); test('Returns unallowedValues', async () => { @@ -107,11 +111,13 @@ describe('getUnallowedFieldValuesRoute route', () => { describe('request validation', () => { let server: ReturnType; + let logger: MockedLogger; beforeEach(() => { server = serverMock.create(); + logger = loggerMock.create(); - getUnallowedFieldValuesRoute(server.router); + getUnallowedFieldValuesRoute(server.router, logger); }); test('disallows invalid pattern', () => { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.ts index be504a812e16b..db8887c2dfa66 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.ts @@ -5,40 +5,46 @@ * 2.0. */ -import { IRouter } from '@kbn/core/server'; +import { IRouter, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { getUnallowedFieldValues } from '../lib'; import { buildResponse } from '../lib/build_response'; -import { GET_UNALLOWED_FIELD_VALUES } from '../../common/constants'; +import { GET_UNALLOWED_FIELD_VALUES, INTERNAL_API_VERSION } from '../../common/constants'; import { buildRouteValidation } from '../schemas/common'; import { GetUnallowedFieldValuesBody } from '../schemas/get_unallowed_field_values'; -export const getUnallowedFieldValuesRoute = (router: IRouter) => { - router.post( - { +export const getUnallowedFieldValuesRoute = (router: IRouter, logger: Logger) => { + router.versioned + .post({ path: GET_UNALLOWED_FIELD_VALUES, - validate: { body: buildRouteValidation(GetUnallowedFieldValuesBody) }, - }, - async (context, request, response) => { - const resp = buildResponse(response); - const esClient = (await context.core).elasticsearch.client.asCurrentUser; + access: 'internal', + }) + .addVersion( + { + version: INTERNAL_API_VERSION, + validate: { request: { body: buildRouteValidation(GetUnallowedFieldValuesBody) } }, + }, + async (context, request, response) => { + const resp = buildResponse(response); + const esClient = (await context.core).elasticsearch.client.asCurrentUser; - try { - const items = request.body; + try { + const items = request.body; - const { responses } = await getUnallowedFieldValues(esClient, items); - return response.ok({ - body: responses, - }); - } catch (err) { - const error = transformError(err); + const { responses } = await getUnallowedFieldValues(esClient, items); + return response.ok({ + body: responses, + }); + } catch (err) { + const error = transformError(err); + logger.error(error.message); - return resp.error({ - body: error.message, - statusCode: error.statusCode, - }); + return resp.error({ + body: error.message, + statusCode: error.statusCode, + }); + } } - } - ); + ); }; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/common.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/common.ts index 00e97a9326c5e..57dc45d4071f7 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/common.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/common.ts @@ -7,7 +7,7 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import type * as rt from 'io-ts'; +import * as rt from 'io-ts'; import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import type { RouteValidationFunction, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json b/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json index b5c1ad152b232..c0603ef91df6b 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json +++ b/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json @@ -20,6 +20,8 @@ "@kbn/securitysolution-io-ts-utils", "@kbn/securitysolution-io-ts-types", "@kbn/i18n", + "@kbn/core-http-router-server-mocks", + "@kbn/logging-mocks", ], "exclude": [ "target/**/*", From 6d06dc3d2d2fd9440ce474c9f8fdfc45b720fc59 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:34:12 -0500 Subject: [PATCH 04/49] [ML] Fix data drift calculating inaccurate p value when range is not of uniform distribution (#168757) --- .../data_drift/data_drift_page.tsx | 1 - .../document_count_with_dual_brush.tsx | 2 +- .../data_drift/use_data_drift_result.ts | 80 ++++++++++++++++--- 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx index 2411c9096be70..2c45fd37a6858 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx @@ -359,7 +359,6 @@ export const DataDriftPage: FC = ({ initialSettings }) => { label={comparisonIndexPatternLabel} randomSampler={randomSamplerProd} reload={forceRefresh} - brushSelectionUpdateHandler={brushSelectionUpdate} documentCountStats={documentStatsProd.documentCountStats} documentCountStatsSplit={documentStatsProd.documentCountStatsCompare} isBrushCleared={isBrushCleared} diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx index 0d83879c37486..210d364ce7aa6 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx @@ -32,7 +32,7 @@ export interface DocumentCountContentProps | 'interval' | 'chartPointsSplitLabel' > { - brushSelectionUpdateHandler: BrushSelectionUpdateHandler; + brushSelectionUpdateHandler?: BrushSelectionUpdateHandler; documentCountStats?: DocumentCountStats; documentCountStatsSplit?: DocumentCountStats; documentCountStatsSplitLabel?: string; diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts b/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts index 8b22e1d94db33..4588595ffcc4f 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts @@ -29,6 +29,7 @@ import { isDefined } from '@kbn/ml-is-defined'; import { computeChi2PValue, type Histogram } from '@kbn/ml-chi2test'; import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; +import type { AggregationsRangeBucketKeys } from '@elastic/elasticsearch/lib/api/types'; import { createMergedEsQuery } from '../index_data_visualizer/utils/saved_search_utils'; import { useDataVisualizerKibana } from '../kibana_context'; @@ -378,6 +379,7 @@ const fetchComparisonDriftedData = async ({ fields, baselineResponseAggs, baseRequest, + baselineRequest, randomSamplerWrapper, signal, }: { @@ -387,10 +389,19 @@ const fetchComparisonDriftedData = async ({ randomSamplerWrapper: RandomSamplerWrapper; signal: AbortSignal; baselineResponseAggs: object; + baselineRequest: EsRequestParams; }) => { const driftedRequest = { ...baseRequest }; + const driftedRequestAggs: Record = {}; + // Since aggregation is not able to split the values into distinct 5% intervals, + // this breaks our assumption of uniform distributed fractions in the`ks_test`. + // So, to fix this in the general case, we need to run an additional ranges agg to get the doc count for the ranges + // that we get from the percentiles aggregation + // and use it in the bucket_count_ks_test + const rangesRequestAggs: Record = {}; + for (const { field, type } of fields) { if ( isPopulatedObject(baselineResponseAggs, [`${field}_percentiles`]) && @@ -410,19 +421,16 @@ const fetchComparisonDriftedData = async ({ ranges.push({ from: percentiles[idx - 1], to: val }); } }); - // add range and bucket_count_ks_test to the request - driftedRequestAggs[`${field}_ranges`] = { + const rangeAggs = { range: { field, ranges, }, }; - driftedRequestAggs[`${field}_ks_test`] = { - bucket_count_ks_test: { - buckets_path: `${field}_ranges>_count`, - alternative: ['two_sided'], - }, - }; + // add range and bucket_count_ks_test to the request + rangesRequestAggs[`${field}_ranges`] = rangeAggs; + driftedRequestAggs[`${field}_ranges`] = rangeAggs; + // add stats aggregation to the request driftedRequestAggs[`${field}_stats`] = { stats: { @@ -441,6 +449,48 @@ const fetchComparisonDriftedData = async ({ } } + // Compute fractions based on results of ranges + const rangesResp = await dataSearch( + { + ...baselineRequest, + body: { ...baselineRequest.body, aggs: randomSamplerWrapper.wrap(rangesRequestAggs) }, + }, + signal + ); + + const fieldsWithNoOverlap = new Set(); + for (const { field } of fields) { + if (rangesResp.aggregations[`${field}_ranges`]) { + const buckets = rangesResp.aggregations[`${field}_ranges`] + .buckets as AggregationsRangeBucketKeys[]; + + if (buckets) { + const totalSumOfAllBuckets = buckets.reduce((acc, bucket) => acc + bucket.doc_count, 0); + + const fractions = buckets.map((bucket) => ({ + ...bucket, + fraction: bucket.doc_count / totalSumOfAllBuckets, + })); + + if (totalSumOfAllBuckets > 0) { + driftedRequestAggs[`${field}_ks_test`] = { + bucket_count_ks_test: { + buckets_path: `${field}_ranges>_count`, + alternative: ['two_sided'], + ...(totalSumOfAllBuckets > 0 + ? { fractions: fractions.map((bucket) => Number(bucket.fraction.toFixed(3))) } + : {}), + }, + }; + } else { + // If all doc_counts are 0, that means there's no overlap whatsoever + // in which case we don't need to make the ks test agg, because it defaults to astronomically small value + fieldsWithNoOverlap.add(field); + } + } + } + } + const driftedResp = await dataSearch( { ...driftedRequest, @@ -448,6 +498,17 @@ const fetchComparisonDriftedData = async ({ }, signal ); + + fieldsWithNoOverlap.forEach((field) => { + if (driftedResp.aggregations) { + driftedResp.aggregations[`${field}_ks_test`] = { + // Setting -Infinity to represent astronomically small number + // which would be represented as < 0.000001 in table + two_sided: -Infinity, + }; + } + }); + return driftedResp; }; @@ -678,7 +739,7 @@ export const useFetchDataComparisonResult = ( setResult({ data: undefined, status: FETCH_STATUS.LOADING, error: undefined }); - // Place holder for when there might be difference data views in the future + // Placeholder for when there might be difference data views in the future const referenceIndex = initialSettings ? initialSettings.reference : currentDataView?.getIndexPattern(); @@ -802,6 +863,7 @@ export const useFetchDataComparisonResult = ( fetchComparisonDriftedData({ dataSearch, baseRequest: driftedRequest, + baselineRequest, baselineResponseAggs, fields: chunkedFields, randomSamplerWrapper: prodRandomSamplerWrapper, From bc36c699d4967f3e55b920cc6999c7037a879047 Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Tue, 17 Oct 2023 15:52:30 -0400 Subject: [PATCH 05/49] Move Security's codegen step into the checks command as it has a short runtime (#168260) ## Summary Security runs a code generator script on PRs. It only takes a few seconds. This change moves it to the 'checks' command. This will save on build agents. ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) Co-authored-by: Jon --- .buildkite/pipelines/pull_request/base.yml | 7 ------- .buildkite/scripts/steps/checks.sh | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index abc6436e7ee0a..6437742d31c15 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -212,13 +212,6 @@ steps: automatic: false soft_fail: true - - command: .buildkite/scripts/steps/code_generation/security_solution_codegen.sh - label: 'Security Solution OpenAPI codegen' - agents: - queue: n2-2-spot - timeout_in_minutes: 60 - parallelism: 1 - - command: .buildkite/scripts/steps/functional/osquery_cypress_burn.sh label: 'Osquery Cypress Tests, burning changed specs' agents: diff --git a/.buildkite/scripts/steps/checks.sh b/.buildkite/scripts/steps/checks.sh index 12077902c1c13..c2758eb52c738 100755 --- a/.buildkite/scripts/steps/checks.sh +++ b/.buildkite/scripts/steps/checks.sh @@ -22,3 +22,4 @@ export DISABLE_BOOTSTRAP_VALIDATION=false .buildkite/scripts/steps/checks/test_hardening.sh .buildkite/scripts/steps/checks/ftr_configs.sh .buildkite/scripts/steps/checks/saved_objects_compat_changes.sh +.buildkite/scripts/steps/code_generation/security_solution_codegen.sh From c369545439774c3a4405fd16bc72209b6952c082 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 17 Oct 2023 16:03:27 -0400 Subject: [PATCH 06/49] [Fleet] Fix integration sticky column after EUI upgrade (#169111) --- .../epm/components/package_list_grid/controls.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx index cb5c61ff57a14..c50d396c143f8 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx @@ -6,6 +6,7 @@ */ import React, { type ReactNode } from 'react'; +import styled from 'styled-components'; import { EuiFlexGroup, EuiSpacer, EuiTitle } from '@elastic/eui'; @@ -14,6 +15,10 @@ interface ControlsColumnProps { title: string | undefined; } +const FlexGroupWithMaxHeight = styled(EuiFlexGroup)` + max-height: calc(100vh - 120px); +`; + export const ControlsColumn = ({ controls, title }: ControlsColumnProps) => { let titleContent; if (title) { @@ -27,9 +32,9 @@ export const ControlsColumn = ({ controls, title }: ControlsColumnProps) => { ); } return ( - + {titleContent} {controls} - + ); }; From bdf064e075b1d393ae3a7168b14b50d182868ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:37:56 +0200 Subject: [PATCH 07/49] [Remote clusters] Fix empty states (#169015) ## Summary I noticed that Remote clusters UI has a wrapper on empty states which is probably a regression from the EuiPage deprecations migration. This PR fixes the list and edit views. See screenshots below.

#### List empty state Before Screenshot 2023-10-16 at 19 27 12 After Screenshot 2023-10-16 at 19 42 24 #### List error Before Screenshot 2023-10-16 at 19 34 07 After Screenshot 2023-10-16 at 19 45 18 #### List error permissions Before Screenshot 2023-10-16 at 19 27 50 After Screenshot 2023-10-16 at 19 44 14 #### List loading Before Screenshot 2023-10-16 at 20 08 54 After Screenshot 2023-10-16 at 20 10 18 #### Edit form error Before Screenshot 2023-10-16 at 19 59 54 After Screenshot 2023-10-16 at 20 03 59 #### Edit form loading Before Screenshot 2023-10-16 at 20 00 34 After Screenshot 2023-10-16 at 20 03 41
--- .../section_loading/section_loading.tsx | 17 +- .../remote_cluster_edit.js | 140 ++++++------- .../remote_cluster_list.js | 198 ++++++++---------- 3 files changed, 173 insertions(+), 182 deletions(-) diff --git a/src/plugins/es_ui_shared/public/components/section_loading/section_loading.tsx b/src/plugins/es_ui_shared/public/components/section_loading/section_loading.tsx index 119e053312dc8..cbdaf444ca228 100644 --- a/src/plugins/es_ui_shared/public/components/section_loading/section_loading.tsx +++ b/src/plugins/es_ui_shared/public/components/section_loading/section_loading.tsx @@ -15,13 +15,24 @@ import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; interface Props { inline?: boolean; children: React.ReactNode; + 'data-test-subj'?: string; [key: string]: any; } -export const SectionLoading: React.FunctionComponent = ({ inline, children, ...rest }) => { +export const SectionLoading: React.FunctionComponent = ({ + inline, + 'data-test-subj': dataTestSubj, + children, + ...rest +}) => { if (inline) { return ( - + @@ -38,7 +49,7 @@ export const SectionLoading: React.FunctionComponent = ({ inline, childre } body={{children}} - data-test-subj="sectionLoading" + data-test-subj={dataTestSubj ?? 'sectionLoading'} /> ); }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js index 02ae974380683..04daad5dee8fc 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js @@ -96,55 +96,51 @@ export class RemoteClusterEdit extends Component { if (isLoading) { return ( - - - - - + + + ); } if (!cluster) { return ( - - - - - } - body={ -

- -

- } - actions={ - - - - } - /> -
+ + + + } + body={ +

+ +

+ } + actions={ + + + + } + /> ); } @@ -152,36 +148,34 @@ export class RemoteClusterEdit extends Component { if (isConfiguredByNode) { return ( - - - - - } - body={ -

- + + + } + body={ +

+ -

- } - actions={ - - - - } - /> -
+ /> +

+ } + actions={ + + + + } + /> ); } diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js index 06948e25a0583..21ed52d08fa83 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js @@ -93,28 +93,26 @@ export class RemoteClusterList extends Component { renderNoPermission() { return ( - - - - - } - body={ -

- -

- } - /> -
+ + + + } + body={ +

+ +

+ } + /> ); } @@ -124,103 +122,91 @@ export class RemoteClusterList extends Component { const { statusCode, error: errorString } = error.body; return ( - - - - - } - body={ -

- {statusCode} {errorString} -

- } - /> -
+ + + + } + body={ +

+ {statusCode} {errorString} +

+ } + /> ); } renderEmpty() { return ( - - - - - } - body={ -

- + + + } + body={ +

+ -

- } - actions={ - - - - } - footer={ - <> - - - - - {' '} - + /> +

+ } + actions={ + + + + } + footer={ + <> + + -
- - } - /> -
+ + {' '} + + + + + } + /> ); } renderLoading() { return ( - - - - - + + + ); } From 2932b77eec59bcb923814009d2904070f401e4de Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 17 Oct 2023 15:57:36 -0500 Subject: [PATCH 08/49] Fix storybooks (#169167) https://buildkite.com/elastic/kibana-on-merge/builds/36947#018b3ee2-2f5a-4a69-9d88-8f074cd1b4fa/229-1431 Uses the fix found by mistic at https://github.com/elastic/kibana/pull/168043 --- .../connector_selector_inline/action_type_selector_modal.tsx | 2 +- .../security_solution/public/assistant/get_comments/index.tsx | 2 +- .../right/components/session_preview_container.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx index 6184525877d1c..02db4bf391c14 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/action_type_selector_modal.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { ActionType } from '@kbn/actions-plugin/common'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { css } from '@emotion/css/dist/emotion-css.cjs'; +import { css } from '@emotion/css'; import * as i18n from '../translations'; interface Props { diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx index d91caae855d22..b87de5bc64874 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx @@ -11,7 +11,7 @@ import { EuiAvatar, EuiMarkdownFormat, EuiText, tint } from '@elastic/eui'; import React from 'react'; import { AssistantAvatar } from '@kbn/elastic-assistant'; -import { css } from '@emotion/react/dist/emotion-react.cjs'; +import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; import { CommentActions } from '../comment_actions'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx index 101c067ad661d..10250e74c383c 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx @@ -10,7 +10,7 @@ import { TimelineTabs } from '@kbn/securitysolution-data-table'; import { useDispatch } from 'react-redux'; import { EuiLink, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { css } from '@emotion/css/dist/emotion-css.cjs'; +import { css } from '@emotion/css'; import { useLicense } from '../../../../common/hooks/use_license'; import { SessionPreview } from './session_preview'; import { useSessionPreview } from '../hooks/use_session_preview'; From befbe10fd37afb3c2dbc7382c75d155a2165d5ff Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 17 Oct 2023 15:58:58 -0600 Subject: [PATCH 09/49] [SLO] Add timeslice metric indicator (#168539) ## Summary This PR adds the new Timeslice Metric indicator for SLOs. Due to the nature of these statistical aggregations, this indicator requires the budgeting method to be set to `timeslices`; we ignore the timeslice threshold in favor of the threshold set in the metric definition. Users can create SLOs based on the following aggregations: - Average - Min - Max - Sum - Cardinality - Percentile - Document count - Std. Deviation - Last Value Other notable feature include: - The ability to define an equation which supports basic math and logic - Users can define a threshold based on the equation for good slices vs bad slices image ### Counter Metric Example image CC: @lucasmoore --------- Co-authored-by: Kevin Delemme --- .../kbn-slo-schema/src/rest_specs/slo.ts | 12 + .../kbn-slo-schema/src/schema/indicators.ts | 88 ++- .../docs/openapi/slo/bundled.json | 216 +++++- .../docs/openapi/slo/bundled.yaml | 160 ++++ .../schemas/create_slo_request.yaml | 1 + ...indicator_properties_timeslice_metric.yaml | 64 ++ .../slo/components/schemas/slo_response.yaml | 6 +- ...eslice_metric_basic_metric_with_field.yaml | 25 + .../timeslice_metric_doc_count_metric.yaml | 21 + .../timeslice_metric_percentile_metric.yaml | 30 + .../schemas/update_slo_request.yaml | 1 + .../components/overview/overview.tsx | 30 +- .../components/common/data_preview_chart.tsx | 90 ++- .../custom_metric/metric_indicator.tsx | 4 +- .../slo_edit_form_indicator_section.tsx | 3 + .../slo_edit_form_objective_section.tsx | 17 + ...edit_form_objective_section_timeslices.tsx | 4 +- .../timeslice_metric/metric_indicator.tsx | 303 ++++++++ .../timeslice_metric/metric_input.tsx | 263 +++++++ .../timeslice_metric_indicator.tsx | 163 ++++ .../public/pages/slo_edit/constants.ts | 75 ++ .../slo_edit/helpers/aggregation_options.ts | 84 +++ .../helpers/process_slo_form_values.ts | 6 + .../hooks/use_section_form_validation.ts | 40 +- .../slo_edit/hooks/use_unregister_fields.ts | 18 + .../observability/public/utils/slo/labels.ts | 10 + .../observability/server/routes/slo/route.ts | 2 + ..._metric_indicator_aggregation.test.ts.snap | 213 ++++++ ...get_custom_metric_indicator_aggregation.ts | 2 +- ...slice_metric_indicator_aggregation.test.ts | 108 +++ ..._timeslice_metric_indicator_aggregation.ts | 130 ++++ .../server/services/slo/aggregations/index.ts | 1 + .../server/services/slo/fixtures/slo.ts | 27 +- .../server/services/slo/get_preview_data.ts | 45 ++ .../timeslice_metric.test.ts.snap | 697 ++++++++++++++++++ .../slo/transform_generators/index.ts | 1 + .../timeslice_metric.test.ts | 178 +++++ .../transform_generators/timeslice_metric.ts | 116 +++ 38 files changed, 3225 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_timeslice_metric.yaml create mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_basic_metric_with_field.yaml create mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_doc_count_metric.yaml create mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_percentile_metric.yaml create mode 100644 x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_indicator.tsx create mode 100644 x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_input.tsx create mode 100644 x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx create mode 100644 x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts create mode 100644 x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_timeslice_metric_indicator_aggregation.test.ts.snap create mode 100644 x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.test.ts create mode 100644 x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.ts create mode 100644 x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/timeslice_metric.test.ts.snap create mode 100644 x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.test.ts create mode 100644 x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.ts diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts index 706213003250b..155fea1aeb6d0 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -19,6 +19,7 @@ import { indicatorTypesSchema, kqlCustomIndicatorSchema, metricCustomIndicatorSchema, + timesliceMetricIndicatorSchema, objectiveSchema, optionalSettingsSchema, previewDataSchema, @@ -28,6 +29,9 @@ import { tagsSchema, timeWindowSchema, timeWindowTypeSchema, + timesliceMetricBasicMetricWithField, + timesliceMetricDocCountMetric, + timesliceMetricPercentileMetric, } from '../schema'; const createSLOParamsSchema = t.type({ @@ -270,6 +274,10 @@ type Indicator = t.OutputOf; type APMTransactionErrorRateIndicator = t.OutputOf; type APMTransactionDurationIndicator = t.OutputOf; type MetricCustomIndicator = t.OutputOf; +type TimesliceMetricIndicator = t.OutputOf; +type TimesliceMetricBasicMetricWithField = t.OutputOf; +type TimesliceMetricDocCountMetric = t.OutputOf; +type TimesclieMetricPercentileMetric = t.OutputOf; type HistogramIndicator = t.OutputOf; type KQLCustomIndicator = t.OutputOf; @@ -327,6 +335,10 @@ export type { IndicatorType, Indicator, MetricCustomIndicator, + TimesliceMetricIndicator, + TimesliceMetricBasicMetricWithField, + TimesclieMetricPercentileMetric, + TimesliceMetricDocCountMetric, HistogramIndicator, KQLCustomIndicator, TimeWindow, diff --git a/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts b/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts index 07b9c69f4fb97..f8d795275acc6 100644 --- a/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts +++ b/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts @@ -59,6 +59,83 @@ const kqlCustomIndicatorSchema = t.type({ ]), }); +const timesliceMetricComparatorMapping = { + GT: '>', + GTE: '>=', + LT: '<', + LTE: '<=', +}; + +const timesliceMetricComparator = t.keyof(timesliceMetricComparatorMapping); + +const timesliceMetricBasicMetricWithField = t.intersection([ + t.type({ + name: t.string, + aggregation: t.keyof({ + avg: true, + max: true, + min: true, + sum: true, + cardinality: true, + last_value: true, + std_deviation: true, + }), + field: t.string, + }), + t.partial({ + filter: t.string, + }), +]); + +const timesliceMetricDocCountMetric = t.intersection([ + t.type({ + name: t.string, + aggregation: t.literal('doc_count'), + }), + t.partial({ + filter: t.string, + }), +]); + +const timesliceMetricPercentileMetric = t.intersection([ + t.type({ + name: t.string, + aggregation: t.literal('percentile'), + field: t.string, + percentile: t.number, + }), + t.partial({ + filter: t.string, + }), +]); + +const timesliceMetricMetricDef = t.union([ + timesliceMetricBasicMetricWithField, + timesliceMetricDocCountMetric, + timesliceMetricPercentileMetric, +]); + +const timesliceMetricDef = t.type({ + metrics: t.array(timesliceMetricMetricDef), + equation: t.string, + threshold: t.number, + comparator: timesliceMetricComparator, +}); +const timesliceMetricIndicatorTypeSchema = t.literal('sli.metric.timeslice'); +const timesliceMetricIndicatorSchema = t.type({ + type: timesliceMetricIndicatorTypeSchema, + params: t.intersection([ + t.type({ + index: t.string, + metric: timesliceMetricDef, + timestampField: t.string, + }), + t.partial({ + filter: t.string, + }), + ]), +}); + const metricCustomValidAggregations = t.keyof({ sum: true, }); @@ -149,6 +226,7 @@ const indicatorTypesSchema = t.union([ apmTransactionErrorRateIndicatorTypeSchema, kqlCustomIndicatorTypeSchema, metricCustomIndicatorTypeSchema, + timesliceMetricIndicatorTypeSchema, histogramIndicatorTypeSchema, ]); @@ -176,6 +254,7 @@ const indicatorSchema = t.union([ apmTransactionErrorRateIndicatorSchema, kqlCustomIndicatorSchema, metricCustomIndicatorSchema, + timesliceMetricIndicatorSchema, histogramIndicatorSchema, ]); @@ -186,8 +265,15 @@ export { apmTransactionErrorRateIndicatorTypeSchema, kqlCustomIndicatorSchema, kqlCustomIndicatorTypeSchema, - metricCustomIndicatorTypeSchema, metricCustomIndicatorSchema, + metricCustomIndicatorTypeSchema, + timesliceMetricComparatorMapping, + timesliceMetricIndicatorSchema, + timesliceMetricIndicatorTypeSchema, + timesliceMetricMetricDef, + timesliceMetricBasicMetricWithField, + timesliceMetricDocCountMetric, + timesliceMetricPercentileMetric, histogramIndicatorTypeSchema, histogramIndicatorSchema, indicatorSchema, diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.json b/x-pack/plugins/observability/docs/openapi/slo/bundled.json index 3ba6ab7762e93..b4f52b032a9fc 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.json +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.json @@ -1240,6 +1240,210 @@ } } }, + "timeslice_metric_basic_metric_with_field": { + "title": "Timeslice Metric Basic Metric with Field", + "required": [ + "name", + "aggregation", + "field" + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the metric. Only valid options are A-Z", + "type": "string", + "example": "A", + "pattern": "^[A-Z]$" + }, + "aggregation": { + "description": "The aggregation type of the metric.", + "type": "string", + "example": "sum", + "enum": [ + "sum", + "avg", + "min", + "max", + "std_deviation", + "last_value", + "cardinality" + ] + }, + "field": { + "description": "The field of the metric.", + "type": "string", + "example": "processor.processed" + }, + "filter": { + "description": "The filter to apply to the metric.", + "type": "string", + "example": "processor.outcome: \"success\"" + } + } + }, + "timeslice_metric_percentile_metric": { + "title": "Timeslice Metric Percentile Metric", + "required": [ + "name", + "aggregation", + "field", + "percentile" + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the metric. Only valid options are A-Z", + "type": "string", + "example": "A", + "pattern": "^[A-Z]$" + }, + "aggregation": { + "description": "The aggregation type of the metric. Only valid option is \"percentile\"", + "type": "string", + "example": "percentile", + "enum": [ + "percentile" + ] + }, + "field": { + "description": "The field of the metric.", + "type": "string", + "example": "processor.processed" + }, + "percentile": { + "description": "The percentile value.", + "type": "number", + "example": 95 + }, + "filter": { + "description": "The filter to apply to the metric.", + "type": "string", + "example": "processor.outcome: \"success\"" + } + } + }, + "timeslice_metric_doc_count_metric": { + "title": "Timeslice Metric Doc Count Metric", + "required": [ + "name", + "aggregation" + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the metric. Only valid options are A-Z", + "type": "string", + "example": "A", + "pattern": "^[A-Z]$" + }, + "aggregation": { + "description": "The aggregation type of the metric. Only valid option is \"doc_count\"", + "type": "string", + "example": "doc_count", + "enum": [ + "doc_count" + ] + }, + "filter": { + "description": "The filter to apply to the metric.", + "type": "string", + "example": "processor.outcome: \"success\"" + } + } + }, + "indicator_properties_timeslice_metric": { + "title": "Timeslice metric", + "required": [ + "type", + "params" + ], + "description": "Defines properties for a timeslice metric indicator type", + "type": "object", + "properties": { + "params": { + "description": "An object containing the indicator parameters.", + "type": "object", + "nullable": false, + "required": [ + "index", + "timestampField", + "metric" + ], + "properties": { + "index": { + "description": "The index or index pattern to use", + "type": "string", + "example": "my-service-*" + }, + "filter": { + "description": "the KQL query to filter the documents with.", + "type": "string", + "example": "field.environment : \"production\" and service.name : \"my-service\"" + }, + "timestampField": { + "description": "The timestamp field used in the source indice.\n", + "type": "string", + "example": "timestamp" + }, + "metric": { + "description": "An object defining the metrics, equation, and threshold to determine if it's a good slice or not\n", + "type": "object", + "required": [ + "metrics", + "equation", + "comparator", + "threshold" + ], + "properties": { + "metrics": { + "description": "List of metrics with their name, aggregation type, and field.", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/timeslice_metric_basic_metric_with_field" + }, + { + "$ref": "#/components/schemas/timeslice_metric_percentile_metric" + }, + { + "$ref": "#/components/schemas/timeslice_metric_doc_count_metric" + } + ] + } + }, + "equation": { + "description": "The equation to calculate the metric.", + "type": "string", + "example": "A" + }, + "comparator": { + "description": "The comparator to use to compare the equation to the threshold.", + "type": "string", + "example": "GT", + "enum": [ + "GT", + "GTE", + "LT", + "LTE" + ] + }, + "threshold": { + "description": "The threshold used to determine if the metric is a good slice or not.", + "type": "number", + "example": 100 + } + } + } + } + }, + "type": { + "description": "The type of indicator.", + "type": "string", + "example": "sli.metric.timeslice" + } + } + }, "time_window": { "title": "Time window", "required": [ @@ -1427,7 +1631,8 @@ "sli.kql.custom": "#/components/schemas/indicator_properties_custom_kql", "sli.apm.transactionDuration": "#/components/schemas/indicator_properties_apm_latency", "sli.metric.custom": "#/components/schemas/indicator_properties_custom_metric", - "sli.histogram.custom": "#/components/schemas/indicator_properties_histogram" + "sli.histogram.custom": "#/components/schemas/indicator_properties_histogram", + "sli.metric.timeslice": "#/components/schemas/indicator_properties_timeslice_metric" } }, "oneOf": [ @@ -1445,6 +1650,9 @@ }, { "$ref": "#/components/schemas/indicator_properties_histogram" + }, + { + "$ref": "#/components/schemas/indicator_properties_timeslice_metric" } ] }, @@ -1661,6 +1869,9 @@ }, { "$ref": "#/components/schemas/indicator_properties_histogram" + }, + { + "$ref": "#/components/schemas/indicator_properties_timeslice_metric" } ] }, @@ -1755,6 +1966,9 @@ }, { "$ref": "#/components/schemas/indicator_properties_histogram" + }, + { + "$ref": "#/components/schemas/indicator_properties_timeslice_metric" } ] }, diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml index c50403e5096f8..8efdbd9dfe2c2 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml @@ -837,6 +837,162 @@ components: description: The type of indicator. type: string example: sli.histogram.custom + timeslice_metric_basic_metric_with_field: + title: Timeslice Metric Basic Metric with Field + required: + - name + - aggregation + - field + type: object + properties: + name: + description: The name of the metric. Only valid options are A-Z + type: string + example: A + pattern: ^[A-Z]$ + aggregation: + description: The aggregation type of the metric. + type: string + example: sum + enum: + - sum + - avg + - min + - max + - std_deviation + - last_value + - cardinality + field: + description: The field of the metric. + type: string + example: processor.processed + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' + timeslice_metric_percentile_metric: + title: Timeslice Metric Percentile Metric + required: + - name + - aggregation + - field + - percentile + type: object + properties: + name: + description: The name of the metric. Only valid options are A-Z + type: string + example: A + pattern: ^[A-Z]$ + aggregation: + description: The aggregation type of the metric. Only valid option is "percentile" + type: string + example: percentile + enum: + - percentile + field: + description: The field of the metric. + type: string + example: processor.processed + percentile: + description: The percentile value. + type: number + example: 95 + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' + timeslice_metric_doc_count_metric: + title: Timeslice Metric Doc Count Metric + required: + - name + - aggregation + type: object + properties: + name: + description: The name of the metric. Only valid options are A-Z + type: string + example: A + pattern: ^[A-Z]$ + aggregation: + description: The aggregation type of the metric. Only valid option is "doc_count" + type: string + example: doc_count + enum: + - doc_count + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' + indicator_properties_timeslice_metric: + title: Timeslice metric + required: + - type + - params + description: Defines properties for a timeslice metric indicator type + type: object + properties: + params: + description: An object containing the indicator parameters. + type: object + nullable: false + required: + - index + - timestampField + - metric + properties: + index: + description: The index or index pattern to use + type: string + example: my-service-* + filter: + description: the KQL query to filter the documents with. + type: string + example: 'field.environment : "production" and service.name : "my-service"' + timestampField: + description: | + The timestamp field used in the source indice. + type: string + example: timestamp + metric: + description: | + An object defining the metrics, equation, and threshold to determine if it's a good slice or not + type: object + required: + - metrics + - equation + - comparator + - threshold + properties: + metrics: + description: List of metrics with their name, aggregation type, and field. + type: array + items: + anyOf: + - $ref: '#/components/schemas/timeslice_metric_basic_metric_with_field' + - $ref: '#/components/schemas/timeslice_metric_percentile_metric' + - $ref: '#/components/schemas/timeslice_metric_doc_count_metric' + equation: + description: The equation to calculate the metric. + type: string + example: A + comparator: + description: The comparator to use to compare the equation to the threshold. + type: string + example: GT + enum: + - GT + - GTE + - LT + - LTE + threshold: + description: The threshold used to determine if the metric is a good slice or not. + type: number + example: 100 + type: + description: The type of indicator. + type: string + example: sli.metric.timeslice time_window: title: Time window required: @@ -988,12 +1144,14 @@ components: sli.apm.transactionDuration: '#/components/schemas/indicator_properties_apm_latency' sli.metric.custom: '#/components/schemas/indicator_properties_custom_metric' sli.histogram.custom: '#/components/schemas/indicator_properties_histogram' + sli.metric.timeslice: '#/components/schemas/indicator_properties_timeslice_metric' oneOf: - $ref: '#/components/schemas/indicator_properties_custom_kql' - $ref: '#/components/schemas/indicator_properties_apm_availability' - $ref: '#/components/schemas/indicator_properties_apm_latency' - $ref: '#/components/schemas/indicator_properties_custom_metric' - $ref: '#/components/schemas/indicator_properties_histogram' + - $ref: '#/components/schemas/indicator_properties_timeslice_metric' timeWindow: $ref: '#/components/schemas/time_window' budgetingMethod: @@ -1150,6 +1308,7 @@ components: - $ref: '#/components/schemas/indicator_properties_apm_latency' - $ref: '#/components/schemas/indicator_properties_custom_metric' - $ref: '#/components/schemas/indicator_properties_histogram' + - $ref: '#/components/schemas/indicator_properties_timeslice_metric' timeWindow: $ref: '#/components/schemas/time_window' budgetingMethod: @@ -1212,6 +1371,7 @@ components: - $ref: '#/components/schemas/indicator_properties_apm_latency' - $ref: '#/components/schemas/indicator_properties_custom_metric' - $ref: '#/components/schemas/indicator_properties_histogram' + - $ref: '#/components/schemas/indicator_properties_timeslice_metric' timeWindow: $ref: '#/components/schemas/time_window' budgetingMethod: diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml index f14a1a134abd8..c3a848fe52133 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml @@ -27,6 +27,7 @@ properties: - $ref: "indicator_properties_apm_latency.yaml" - $ref: "indicator_properties_custom_metric.yaml" - $ref: 'indicator_properties_histogram.yaml' + - $ref: 'indicator_properties_timeslice_metric.yaml' timeWindow: $ref: "time_window.yaml" budgetingMethod: diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_timeslice_metric.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_timeslice_metric.yaml new file mode 100644 index 0000000000000..712420f059fdd --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_timeslice_metric.yaml @@ -0,0 +1,64 @@ +title: Timeslice metric +required: + - type + - params +description: Defines properties for a timeslice metric indicator type +type: object +properties: + params: + description: An object containing the indicator parameters. + type: object + nullable: false + required: + - index + - timestampField + - metric + properties: + index: + description: The index or index pattern to use + type: string + example: my-service-* + filter: + description: the KQL query to filter the documents with. + type: string + example: 'field.environment : "production" and service.name : "my-service"' + timestampField: + description: > + The timestamp field used in the source indice. + type: string + example: timestamp + metric: + description: > + An object defining the metrics, equation, and threshold to determine if it's a good slice or not + type: object + required: + - metrics + - equation + - comparator + - threshold + properties: + metrics: + description: List of metrics with their name, aggregation type, and field. + type: array + items: + anyOf: + - $ref: './timeslice_metric_basic_metric_with_field.yaml' + - $ref: './timeslice_metric_percentile_metric.yaml' + - $ref: './timeslice_metric_doc_count_metric.yaml' + equation: + description: The equation to calculate the metric. + type: string + example: A + comparator: + description: The comparator to use to compare the equation to the threshold. + type: string + example: GT + enum: [GT, GTE, LT, LTE] + threshold: + description: The threshold used to determine if the metric is a good slice or not. + type: number + example: 100 + type: + description: The type of indicator. + type: string + example: sli.metric.timeslice diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml index da81009bc20b3..bd58e88c7b641 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml @@ -37,14 +37,16 @@ properties: sli.apm.transactionErrorRate: './indicator_properties_apm_availability.yaml' sli.kql.custom: './indicator_properties_custom_kql.yaml' sli.apm.transactionDuration: './indicator_properties_apm_latency.yaml' - sli.metric.custom: 'indicator_properties_custom_metric.yaml' - sli.histogram.custom: 'indicator_properties_histogram.yaml' + sli.metric.custom: './indicator_properties_custom_metric.yaml' + sli.histogram.custom: './indicator_properties_histogram.yaml' + sli.metric.timeslice: './indicator_properties_timeslice_metric.yaml' oneOf: - $ref: "indicator_properties_custom_kql.yaml" - $ref: "indicator_properties_apm_availability.yaml" - $ref: "indicator_properties_apm_latency.yaml" - $ref: "indicator_properties_custom_metric.yaml" - $ref: "indicator_properties_histogram.yaml" + - $ref: "indicator_properties_timeslice_metric.yaml" timeWindow: $ref: "time_window.yaml" budgetingMethod: diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_basic_metric_with_field.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_basic_metric_with_field.yaml new file mode 100644 index 0000000000000..570f4b4dda905 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_basic_metric_with_field.yaml @@ -0,0 +1,25 @@ +title: Timeslice Metric Basic Metric with Field +required: + - name + - aggregation + - field +type: object +properties: + name: + description: The name of the metric. Only valid options are A-Z + type: string + example: A + pattern: "^[A-Z]$" + aggregation: + description: The aggregation type of the metric. + type: string + example: sum + enum: [sum, avg, min, max, std_deviation, last_value, cardinality] + field: + description: The field of the metric. + type: string + example: processor.processed + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_doc_count_metric.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_doc_count_metric.yaml new file mode 100644 index 0000000000000..76417fd111975 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_doc_count_metric.yaml @@ -0,0 +1,21 @@ +title: Timeslice Metric Doc Count Metric +required: + - name + - aggregation +type: object +properties: + name: + description: The name of the metric. Only valid options are A-Z + type: string + example: A + pattern: "^[A-Z]$" + aggregation: + description: The aggregation type of the metric. Only valid option is "doc_count" + type: string + example: doc_count + enum: [doc_count] + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' + diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_percentile_metric.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_percentile_metric.yaml new file mode 100644 index 0000000000000..c55b7e1c5abb8 --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/timeslice_metric_percentile_metric.yaml @@ -0,0 +1,30 @@ +title: Timeslice Metric Percentile Metric +required: + - name + - aggregation + - field + - percentile +type: object +properties: + name: + description: The name of the metric. Only valid options are A-Z + type: string + example: A + pattern: "^[A-Z]$" + aggregation: + description: The aggregation type of the metric. Only valid option is "percentile" + type: string + example: percentile + enum: [percentile] + field: + description: The field of the metric. + type: string + example: processor.processed + percentile: + description: The percentile value. + type: number + example: 95 + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml index ddeb2e39159a3..8d2c61c7b2249 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml @@ -17,6 +17,7 @@ properties: - $ref: "indicator_properties_apm_latency.yaml" - $ref: "indicator_properties_custom_metric.yaml" - $ref: "indicator_properties_histogram.yaml" + - $ref: "indicator_properties_timeslice_metric.yaml" timeWindow: $ref: "time_window.yaml" budgetingMethod: diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/overview/overview.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/overview/overview.tsx index f3a6abb829984..03d76b8dc2a7d 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/overview/overview.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/overview/overview.tsx @@ -94,16 +94,26 @@ export function Overview({ slo }: Props) { ) : ( {BUDGETING_METHOD_TIMESLICES} ( - {i18n.translate( - 'xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethodDetails', - { - defaultMessage: '{duration} slices, {target} target', - values: { - duration: toDurationLabel(slo.objective.timesliceWindow!), - target: numeral(slo.objective.timesliceTarget!).format(percentFormat), - }, - } - )} + {slo.indicator.type === 'sli.metric.timeslice' + ? i18n.translate( + 'xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethodDetailsForTimesliceMetric', + { + defaultMessage: '{duration} slices', + values: { + duration: toDurationLabel(slo.objective.timesliceWindow!), + }, + } + ) + : i18n.translate( + 'xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethodDetails', + { + defaultMessage: '{duration} slices, {target} target', + values: { + duration: toDurationLabel(slo.objective.timesliceWindow!), + target: numeral(slo.objective.timesliceTarget!).format(percentFormat), + }, + } + )} ) ) diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/data_preview_chart.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/data_preview_chart.tsx index 8f6fe11cae333..7dc2e00f60829 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/common/data_preview_chart.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/common/data_preview_chart.tsx @@ -5,7 +5,18 @@ * 2.0. */ -import { AreaSeries, Axis, Chart, Position, ScaleType, Settings, Tooltip } from '@elastic/charts'; +import { + AnnotationDomainType, + AreaSeries, + Axis, + Chart, + LineAnnotation, + Position, + RectAnnotation, + ScaleType, + Settings, + Tooltip, +} from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, @@ -22,12 +33,27 @@ import moment from 'moment'; import React from 'react'; import { useFormContext } from 'react-hook-form'; import { FormattedMessage } from '@kbn/i18n-react'; +import { min, max } from 'lodash'; import { useKibana } from '../../../../utils/kibana_react'; import { useDebouncedGetPreviewData } from '../../hooks/use_preview'; import { useSectionFormValidation } from '../../hooks/use_section_form_validation'; import { CreateSLOForm } from '../../types'; -export function DataPreviewChart() { +interface DataPreviewChartProps { + formatPattern?: string; + threshold?: number; + thresholdDirection?: 'above' | 'below'; + thresholdColor?: string; + thresholdMessage?: string; +} + +export function DataPreviewChart({ + formatPattern, + threshold, + thresholdDirection, + thresholdColor, + thresholdMessage, +}: DataPreviewChartProps) { const { watch, getFieldState, formState, getValues } = useFormContext(); const { charts, uiSettings } = useKibana().services; const { isIndicatorSectionValid } = useSectionFormValidation({ @@ -47,7 +73,22 @@ export function DataPreviewChart() { const theme = charts.theme.useChartsTheme(); const baseTheme = charts.theme.useChartsBaseTheme(); const dateFormat = uiSettings.get('dateFormat'); - const percentFormat = uiSettings.get('format:percent:defaultPattern'); + const numberFormat = + formatPattern != null + ? formatPattern + : (uiSettings.get('format:percent:defaultPattern') as string); + + const values = (previewData || []).map((row) => row.sliValue); + const maxValue = max(values); + const minValue = min(values); + const domain = { + fit: true, + min: + threshold != null && minValue != null && threshold < minValue ? threshold : minValue || NaN, + max: + threshold != null && maxValue != null && threshold > maxValue ? threshold : maxValue || NaN, + }; + const title = ( <> @@ -85,6 +126,39 @@ export function DataPreviewChart() { ); } + const annotation = threshold != null && ( + <> + + + + ); + return ( {title} @@ -127,6 +201,8 @@ export function DataPreviewChart() { locale={i18n.getLocale()} /> + {annotation} + numeral(d).format(percentFormat)} - domain={{ - fit: true, - min: NaN, - max: NaN, - }} + tickFormat={(d) => numeral(d).format(numberFormat)} + domain={domain} /> { const defaultEquation = createEquationFromMetric(previousNames); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_indicator_section.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_indicator_section.tsx index 1be3a5b10e537..3c87168792303 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_indicator_section.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_indicator_section.tsx @@ -18,6 +18,7 @@ import { CustomKqlIndicatorTypeForm } from './custom_kql/custom_kql_indicator_ty import { CustomMetricIndicatorTypeForm } from './custom_metric/custom_metric_type_form'; import { HistogramIndicatorTypeForm } from './histogram/histogram_indicator_type_form'; import { maxWidth } from './slo_edit_form'; +import { TimesliceMetricIndicatorTypeForm } from './timeslice_metric/timeslice_metric_indicator'; interface SloEditFormIndicatorSectionProps { isEditMode: boolean; @@ -39,6 +40,8 @@ export function SloEditFormIndicatorSection({ isEditMode }: SloEditFormIndicator return ; case 'sli.histogram.custom': return ; + case 'sli.metric.timeslice': + return ; default: return null; } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objective_section.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objective_section.tsx index 951e879f43cf1..ad1f84183a138 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objective_section.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_objective_section.tsx @@ -6,6 +6,7 @@ */ import { + EuiCallOut, EuiFieldNumber, EuiFlexGrid, EuiFlexItem, @@ -20,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import { TimeWindow } from '@kbn/slo-schema'; import React, { useEffect, useState } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; +import { FormattedMessage } from '@kbn/i18n-react'; import { BUDGETING_METHOD_OPTIONS, CALENDARALIGNED_TIMEWINDOW_OPTIONS, @@ -42,6 +44,7 @@ export function SloEditFormObjectiveSection() { const timeWindowTypeSelect = useGeneratedHtmlId({ prefix: 'timeWindowTypeSelect' }); const timeWindowSelect = useGeneratedHtmlId({ prefix: 'timeWindowSelect' }); const timeWindowType = watch('timeWindow.type'); + const indicator = watch('indicator.type'); const [timeWindowTypeState, setTimeWindowTypeState] = useState( defaultValues?.timeWindow?.type @@ -169,6 +172,19 @@ export function SloEditFormObjectiveSection() { + {indicator === 'sli.metric.timeslice' && ( + + +

+ +

+
+ +
+ )} @@ -198,6 +214,7 @@ export function SloEditFormObjectiveSection() { render={({ field: { ref, ...field } }) => ( (); + const { control, getFieldState, watch } = useFormContext(); + const indicator = watch('indicator.type'); return ( <> @@ -47,6 +48,7 @@ export function SloEditFormObjectiveSectionTimeslices() { String.fromCharCode(c)); +const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/; + +const validateEquation = (value: string) => { + const result = value.match(INVALID_EQUATION_REGEX); + return result === null; +}; + +function createEquationFromMetric(names: string[]) { + return names.join(' + '); +} + +const equationLabel = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.equationLabel', + { defaultMessage: 'Equation' } +); + +const equationTooltip = ( + +); + +const thresholdLabel = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.thresholdLabel', + { + defaultMessage: 'Threshold', + } +); + +const thresholdTooltip = ( + +); + +export function MetricIndicator({ indexFields, isLoadingIndex }: MetricIndicatorProps) { + const { control, watch, setValue, register, getFieldState } = useFormContext(); + + const { fields, append, remove } = useFieldArray({ + control, + name: `indicator.params.metric.metrics`, + }); + const equation = watch(`indicator.params.metric.equation`); + const indexPattern = watch('indicator.params.index'); + + const disableAdd = fields?.length === MAX_VARIABLES || !indexPattern; + const disableDelete = fields?.length === 1 || !indexPattern; + + const setDefaultEquationIfUnchanged = (previousNames: string[], nextNames: string[]) => { + const defaultEquation = createEquationFromMetric(previousNames); + if (defaultEquation === equation) { + setValue(`indicator.params.metric.equation`, createEquationFromMetric(nextNames)); + } + }; + + const handleDeleteMetric = (index: number) => () => { + const currentVars = fields.map((m) => m.name) ?? ['A']; + const deletedVar = currentVars[index]; + setDefaultEquationIfUnchanged(currentVars, xor(currentVars, [deletedVar])); + remove(index); + }; + + const handleAddMetric = () => { + const currentVars = fields.map((m) => m.name) ?? ['A']; + const name = first(xor(VAR_NAMES, currentVars))!; + setDefaultEquationIfUnchanged(currentVars, [...currentVars, name]); + append({ ...NEW_TIMESLICE_METRIC, name }); + }; + + return ( + <> + + {fields?.map((metric, index) => ( + + + + + + + + + + + ))} + + + + + + + + + + + + + + ( + + {equationLabel} {equationTooltip} + + } + isInvalid={fieldState.invalid} + error={[ + i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.equation.invalidCharacters', + { + defaultMessage: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } + ), + ]} + > + field.onChange(event.target.value)} + /> + + )} + /> + + + ( + + field.onChange(event.target.value)} + /> + + )} + /> + + + + {thresholdLabel} {thresholdTooltip} + + } + > + ( + field.onChange(Number(event.target.value))} + /> + )} + /> + + + + + + +

+ {i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.equationHelpText', + { + defaultMessage: + 'Supports basic math equations, valid charaters are: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } + )} +

+
+
+
+ + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_input.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_input.tsx new file mode 100644 index 0000000000000..0db4c1d585782 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_input.tsx @@ -0,0 +1,263 @@ +/* + * 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 { + EuiComboBox, + EuiComboBoxOptionOption, + EuiFieldNumber, + EuiFlexItem, + EuiFormRow, + EuiIconTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Controller, useFormContext } from 'react-hook-form'; +import { createOptionsFromFields } from '../../helpers/create_options'; +import { QueryBuilder } from '../common/query_builder'; +import { CreateSLOForm } from '../../types'; +import { AGGREGATION_OPTIONS, aggValueToLabel } from '../../helpers/aggregation_options'; +import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; + +const fieldLabel = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.fieldLabel', + { defaultMessage: 'Field' } +); + +const aggregationLabel = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.aggregationLabel', + { defaultMessage: 'Aggregation' } +); + +const filterLabel = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.filterLabel', + { defaultMessage: 'Filter' } +); + +const fieldTooltip = ( + +); + +const NUMERIC_FIELD_TYPES = ['number', 'histogram']; +const CARDINALITY_FIELD_TYPES = ['number', 'string']; + +interface MetricInputProps { + metricIndex: number; + indexPattern: string; + isLoadingIndex: boolean; + indexFields: Field[]; +} + +export function MetricInput({ + metricIndex: index, + indexPattern, + isLoadingIndex, + indexFields, +}: MetricInputProps) { + const { control, watch } = useFormContext(); + const metric = watch(`indicator.params.metric.metrics.${index}`); + const metricFields = indexFields.filter((field) => + metric.aggregation === 'cardinality' + ? CARDINALITY_FIELD_TYPES.includes(field.type) + : NUMERIC_FIELD_TYPES.includes(field.type) + ); + return ( + <> + + ( + + {aggregationLabel} {metric.name} + + } + isInvalid={fieldState.invalid} + > + { + if (selected.length) { + return field.onChange(selected[0].value); + } + field.onChange(''); + }} + selectedOptions={ + !!indexPattern && + !!field.value && + AGGREGATION_OPTIONS.some((agg) => agg.value === agg.value) + ? [ + { + value: field.value, + label: aggValueToLabel(field.value), + }, + ] + : [] + } + options={AGGREGATION_OPTIONS} + /> + + )} + /> + + {metric.aggregation === 'percentile' && ( + + ( + + {i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.percentileLabel', + { defaultMessage: 'Percentile' } + )}{' '} + {metric.name} + + } + > + onChange(Number(event.target.value))} + /> + + )} + /> + + )} + {metric.aggregation !== 'doc_count' && ( + + ( + + {fieldLabel} {metric.name} {fieldTooltip} + + } + > + { + if (selected.length) { + return field.onChange(selected[0].value); + } + field.onChange(''); + }} + selectedOptions={ + !!indexPattern && + !!field.value && + metricFields.some((metricField) => metricField.name === field.value) + ? [ + { + value: field.value, + label: field.value, + }, + ] + : [] + } + options={createOptionsFromFields(metricFields)} + /> + + )} + /> + + )} + + + } + /> + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx new file mode 100644 index 0000000000000..5d455a601e3d7 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx @@ -0,0 +1,163 @@ +/* + * 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, + EuiHorizontalRule, + EuiIconTip, + EuiSpacer, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; +import { CreateSLOForm } from '../../types'; +import { DataPreviewChart } from '../common/data_preview_chart'; +import { IndexFieldSelector } from '../common/index_field_selector'; +import { QueryBuilder } from '../common/query_builder'; +import { IndexSelection } from '../custom_common/index_selection'; +import { MetricIndicator } from './metric_indicator'; +import { useKibana } from '../../../../utils/kibana_react'; +import { COMPARATOR_MAPPING } from '../../constants'; + +export { NEW_TIMESLICE_METRIC } from './metric_indicator'; + +export function TimesliceMetricIndicatorTypeForm() { + const { watch } = useFormContext(); + const index = watch('indicator.params.index'); + const { isLoading: isIndexFieldsLoading, data: indexFields = [] } = + useFetchIndexPatternFields(index); + const timestampFields = indexFields.filter((field) => field.type === 'date'); + const partitionByFields = indexFields.filter((field) => field.aggregatable); + const { uiSettings } = useKibana().services; + const threshold = watch('indicator.params.metric.threshold'); + const comparator = watch('indicator.params.metric.comparator'); + const { euiTheme } = useEuiTheme(); + + return ( + <> + +

+ +

+
+ + + + + + + + + + + + + + } + /> + + + + + + + + +

+ +

+
+ + +
+ + + + + + + {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { + defaultMessage: 'Partition by', + })}{' '} + + + } + placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', { + defaultMessage: 'Select an optional field to partition by', + })} + isLoading={!!index && isIndexFieldsLoading} + isDisabled={!index} + /> + + +
+ + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/constants.ts b/x-pack/plugins/observability/public/pages/slo_edit/constants.ts index 4c38525784a10..9c5284855c772 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/constants.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/constants.ts @@ -15,6 +15,7 @@ import { IndicatorType, KQLCustomIndicator, MetricCustomIndicator, + TimesliceMetricIndicator, TimeWindow, } from '@kbn/slo-schema'; import { @@ -25,6 +26,7 @@ import { INDICATOR_CUSTOM_KQL, INDICATOR_CUSTOM_METRIC, INDICATOR_HISTOGRAM, + INDICATOR_TIMESLICE_METRIC, } from '../../utils/slo/labels'; import { CreateSLOForm } from './types'; @@ -40,6 +42,10 @@ export const SLI_OPTIONS: Array<{ value: 'sli.metric.custom', text: INDICATOR_CUSTOM_METRIC, }, + { + value: 'sli.metric.timeslice', + text: INDICATOR_TIMESLICE_METRIC, + }, { value: 'sli.histogram.custom', text: INDICATOR_HISTOGRAM, @@ -125,6 +131,21 @@ export const CUSTOM_METRIC_DEFAULT_VALUES: MetricCustomIndicator = { }, }; +export const TIMESLICE_METRIC_DEFAULT_VALUES: TimesliceMetricIndicator = { + type: 'sli.metric.timeslice' as const, + params: { + index: '', + filter: '', + metric: { + metrics: [{ name: 'A', aggregation: 'avg' as const, field: '' }], + equation: 'A', + comparator: 'GT', + threshold: 0, + }, + timestampField: '', + }, +}; + export const HISTOGRAM_DEFAULT_VALUES: HistogramIndicator = { type: 'sli.histogram.custom' as const, params: { @@ -198,3 +219,57 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES_CUSTOM_METRIC: CreateSLOForm = { }, groupBy: ALL_VALUE, }; + +export const COMPARATOR_GT = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.gtLabel', + { + defaultMessage: 'Greater than', + } +); + +export const COMPARATOR_GTE = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.gteLabel', + { + defaultMessage: 'Greater than or equal to', + } +); + +export const COMPARATOR_LT = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.ltLabel', + { + defaultMessage: 'Less than', + } +); + +export const COMPARATOR_LTE = i18n.translate( + 'xpack.observability.slo.sloEdit.sliType.timesliceMetric.lteLabel', + { + defaultMessage: 'Less than or equal to', + } +); + +export const COMPARATOR_MAPPING = { + GT: COMPARATOR_GT, + GTE: COMPARATOR_GTE, + LT: COMPARATOR_LT, + LTE: COMPARATOR_LTE, +}; + +export const COMPARATOR_OPTIONS = [ + { + text: COMPARATOR_GT, + value: 'GT' as const, + }, + { + text: COMPARATOR_GTE, + value: 'GTE' as const, + }, + { + text: COMPARATOR_LT, + value: 'LT' as const, + }, + { + text: COMPARATOR_LTE, + value: 'LTE' as const, + }, +]; diff --git a/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts b/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts new file mode 100644 index 0000000000000..4a3a5fb9cf28a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +export const AGGREGATION_OPTIONS = [ + { + value: 'avg', + label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.average', { + defaultMessage: 'Average', + }), + }, + { + value: 'max', + label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.max', { + defaultMessage: 'Max', + }), + }, + { + value: 'min', + label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.min', { + defaultMessage: 'Min', + }), + }, + { + value: 'sum', + label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.sum', { + defaultMessage: 'Sum', + }), + }, + { + value: 'cardinality', + label: i18n.translate( + 'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.cardinality', + { + defaultMessage: 'Cardinality', + } + ), + }, + { + value: 'last_value', + label: i18n.translate( + 'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.last_value', + { + defaultMessage: 'Last value', + } + ), + }, + { + value: 'std_deviation', + label: i18n.translate( + 'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.std_deviation', + { + defaultMessage: 'Std. Deviation', + } + ), + }, + { + value: 'doc_count', + label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.doc_count', { + defaultMessage: 'Doc count', + }), + }, + { + value: 'percentile', + label: i18n.translate( + 'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.percentile', + { + defaultMessage: 'Percentile', + } + ), + }, +]; + +export function aggValueToLabel(value: string) { + const aggregation = AGGREGATION_OPTIONS.find((agg) => agg.value === value); + if (aggregation) { + return aggregation.label; + } + return value; +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/helpers/process_slo_form_values.ts b/x-pack/plugins/observability/public/pages/slo_edit/helpers/process_slo_form_values.ts index c6de2126adacf..f523cc1ce1ce1 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/helpers/process_slo_form_values.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/helpers/process_slo_form_values.ts @@ -15,6 +15,7 @@ import { CUSTOM_KQL_DEFAULT_VALUES, CUSTOM_METRIC_DEFAULT_VALUES, HISTOGRAM_DEFAULT_VALUES, + TIMESLICE_METRIC_DEFAULT_VALUES, } from '../constants'; import { CreateSLOForm } from '../types'; @@ -132,6 +133,11 @@ function transformPartialIndicatorState( type: 'sli.metric.custom' as const, params: Object.assign({}, CUSTOM_METRIC_DEFAULT_VALUES.params, indicator.params ?? {}), }; + case 'sli.metric.timeslice': + return { + type: 'sli.metric.timeslice' as const, + params: Object.assign({}, TIMESLICE_METRIC_DEFAULT_VALUES.params, indicator.params ?? {}), + }; default: assertNever(indicatorType); } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts b/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts index e0c2652bfc46d..6fede4552d6f8 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { MetricCustomIndicator } from '@kbn/slo-schema'; +import { + MetricCustomIndicator, + timesliceMetricBasicMetricWithField, + TimesliceMetricIndicator, + timesliceMetricPercentileMetric, +} from '@kbn/slo-schema'; import { FormState, UseFormGetFieldState, UseFormGetValues, UseFormWatch } from 'react-hook-form'; import { isObject } from 'lodash'; import { CreateSLOForm } from '../types'; @@ -54,6 +59,39 @@ export function useSectionFormValidation({ getFieldState, getValues, formState, isGoodParamsValid() && isTotalParamsValid(); break; + case 'sli.metric.timeslice': + const isMetricParamsValid = () => { + const data = getValues( + 'indicator.params.metric' + ) as TimesliceMetricIndicator['params']['metric']; + const isEquationValid = !getFieldState('indicator.params.metric.equation').invalid; + const areMetricsValid = + isObject(data) && + (data.metrics ?? []).every((metric) => { + if (timesliceMetricBasicMetricWithField.is(metric)) { + return Boolean(metric.field); + } + if (timesliceMetricPercentileMetric.is(metric)) { + return Boolean(metric.field) && Boolean(metric.percentile); + } + return true; + }); + return isEquationValid && areMetricsValid; + }; + + isIndicatorSectionValid = + ( + [ + 'indicator.params.index', + 'indicator.params.filter', + 'indicator.params.timestampField', + ] as const + ).every((field) => !getFieldState(field).invalid) && + (['indicator.params.index', 'indicator.params.timestampField'] as const).every( + (field) => !!getValues(field) + ) && + isMetricParamsValid(); + break; case 'sli.histogram.custom': const isRangeValid = (type: 'good' | 'total') => { const aggregation = getValues(`indicator.params.${type}.aggregation`); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_unregister_fields.ts b/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_unregister_fields.ts index c15b5cb7fbbfc..d461c841940a4 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_unregister_fields.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_unregister_fields.ts @@ -14,10 +14,12 @@ import { useFetchApmIndex } from '../../../hooks/slo/use_fetch_apm_indices'; import { APM_AVAILABILITY_DEFAULT_VALUES, APM_LATENCY_DEFAULT_VALUES, + BUDGETING_METHOD_OPTIONS, CUSTOM_KQL_DEFAULT_VALUES, CUSTOM_METRIC_DEFAULT_VALUES, HISTOGRAM_DEFAULT_VALUES, SLO_EDIT_FORM_DEFAULT_VALUES, + TIMESLICE_METRIC_DEFAULT_VALUES, } from '../constants'; import { CreateSLOForm } from '../types'; @@ -49,6 +51,22 @@ export function useUnregisterFields({ isEditMode }: { isEditMode: boolean }) { } ); break; + case 'sli.metric.timeslice': + reset( + Object.assign({}, SLO_EDIT_FORM_DEFAULT_VALUES, { + budgetingMethod: BUDGETING_METHOD_OPTIONS[1].value, + objective: { + target: 99, + timesliceTarget: 95, + timesliceWindow: 1, + }, + indicator: TIMESLICE_METRIC_DEFAULT_VALUES, + }), + { + keepDefaultValues: true, + } + ); + break; case 'sli.kql.custom': reset( Object.assign({}, SLO_EDIT_FORM_DEFAULT_VALUES, { diff --git a/x-pack/plugins/observability/public/utils/slo/labels.ts b/x-pack/plugins/observability/public/utils/slo/labels.ts index 40c58e624bb2b..43ed455f5a9e3 100644 --- a/x-pack/plugins/observability/public/utils/slo/labels.ts +++ b/x-pack/plugins/observability/public/utils/slo/labels.ts @@ -21,6 +21,13 @@ export const INDICATOR_CUSTOM_METRIC = i18n.translate( } ); +export const INDICATOR_TIMESLICE_METRIC = i18n.translate( + 'xpack.observability.slo.indicators.timesliceMetric', + { + defaultMessage: 'Timeslice Metric', + } +); + export const INDICATOR_HISTOGRAM = i18n.translate('xpack.observability.slo.indicators.histogram', { defaultMessage: 'Histogram Metric', }); @@ -54,6 +61,9 @@ export function toIndicatorTypeLabel( case 'sli.histogram.custom': return INDICATOR_HISTOGRAM; + case 'sli.metric.timeslice': + return INDICATOR_TIMESLICE_METRIC; + default: assertNever(indicatorType as never); } diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 2dfdcb2308ee3..ade3f1714ddfb 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -49,6 +49,7 @@ import { KQLCustomTransformGenerator, MetricCustomTransformGenerator, TransformGenerator, + TimesliceMetricTransformGenerator, } from '../../services/slo/transform_generators'; import type { ObservabilityRequestHandlerContext } from '../../types'; import { createObservabilityServerRoute } from '../create_observability_server_route'; @@ -59,6 +60,7 @@ const transformGenerators: Record = { 'sli.kql.custom': new KQLCustomTransformGenerator(), 'sli.metric.custom': new MetricCustomTransformGenerator(), 'sli.histogram.custom': new HistogramTransformGenerator(), + 'sli.metric.timeslice': new TimesliceMetricTransformGenerator(), }; const assertPlatinumLicense = async (context: ObservabilityRequestHandlerContext) => { diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_timeslice_metric_indicator_aggregation.test.ts.snap b/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_timeslice_metric_indicator_aggregation.test.ts.snap new file mode 100644 index 0000000000000..8288087ce3327 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_timeslice_metric_indicator_aggregation.test.ts.snap @@ -0,0 +1,213 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GetTimesliceMetricIndicatorAggregation should generate an aggregation for basic metrics 1`] = ` +Object { + "_A": Object { + "aggs": Object { + "metric": Object { + "avg": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "test.category": "test", + }, + }, + ], + }, + }, + }, + "_B": Object { + "aggs": Object { + "metric": Object { + "max": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_C": Object { + "aggs": Object { + "metric": Object { + "min": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_D": Object { + "aggs": Object { + "metric": Object { + "sum": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_E": Object { + "aggs": Object { + "metric": Object { + "cardinality": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>metric", + "B": "_B>metric", + "C": "_C>metric", + "D": "_D>metric", + "E": "_E>metric", + }, + "script": Object { + "lang": "painless", + "source": "(params.A + params.B + params.C + params.D + params.E) / params.A", + }, + }, + }, +} +`; + +exports[`GetTimesliceMetricIndicatorAggregation should generate an aggregation for doc_count 1`] = ` +Object { + "_A": Object { + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "test.category": "test", + }, + }, + ], + }, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>_count", + }, + "script": Object { + "lang": "painless", + "source": "params.A", + }, + }, + }, +} +`; + +exports[`GetTimesliceMetricIndicatorAggregation should generate an aggregation for last_value 1`] = ` +Object { + "_A": Object { + "aggs": Object { + "metric": Object { + "top_metrics": Object { + "metrics": Object { + "field": "test.field", + }, + "sort": Object { + "@timestamp": "desc", + }, + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>metric[test.field]", + }, + "script": Object { + "lang": "painless", + "source": "params.A", + }, + }, + }, +} +`; + +exports[`GetTimesliceMetricIndicatorAggregation should generate an aggregation for percentile 1`] = ` +Object { + "_A": Object { + "aggs": Object { + "metric": Object { + "percentiles": Object { + "field": "test.field", + "keyed": true, + "percents": Array [ + 97, + ], + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>metric[97]", + }, + "script": Object { + "lang": "painless", + "source": "params.A", + }, + }, + }, +} +`; + +exports[`GetTimesliceMetricIndicatorAggregation should generate an aggregation for std_deviation 1`] = ` +Object { + "_A": Object { + "aggs": Object { + "metric": Object { + "extended_stats": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>metric[std_deviation]", + }, + "script": Object { + "lang": "painless", + "source": "params.A", + }, + }, + }, +} +`; diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts b/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts index ceb51bdfef199..73bbb91b1041f 100644 --- a/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts +++ b/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts @@ -37,7 +37,7 @@ export class GetCustomMetricIndicatorAggregation { private convertEquationToPainless(bucketsPath: Record, equation: string) { const workingEquation = equation || Object.keys(bucketsPath).join(' + '); return Object.keys(bucketsPath).reduce((acc, key) => { - return acc.replace(key, `params.${key}`); + return acc.replaceAll(key, `params.${key}`); }, workingEquation); } diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.test.ts b/x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.test.ts new file mode 100644 index 0000000000000..4f73f186fd343 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.test.ts @@ -0,0 +1,108 @@ +/* + * 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 { createTimesliceMetricIndicator } from '../fixtures/slo'; +import { GetTimesliceMetricIndicatorAggregation } from './get_timeslice_metric_indicator_aggregation'; + +describe('GetTimesliceMetricIndicatorAggregation', () => { + it('should generate an aggregation for basic metrics', () => { + const indicator = createTimesliceMetricIndicator( + [ + { + name: 'A', + aggregation: 'avg' as const, + field: 'test.field', + filter: 'test.category: test', + }, + { + name: 'B', + aggregation: 'max' as const, + field: 'test.field', + }, + { + name: 'C', + aggregation: 'min' as const, + field: 'test.field', + }, + { + name: 'D', + aggregation: 'sum' as const, + field: 'test.field', + }, + { + name: 'E', + aggregation: 'cardinality' as const, + field: 'test.field', + }, + ], + '(A + B + C + D + E) / A' + ); + const getIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(indicator); + expect(getIndicatorAggregation.execute('_metric')).toMatchSnapshot(); + }); + + it('should generate an aggregation for doc_count', () => { + const indicator = createTimesliceMetricIndicator( + [ + { + name: 'A', + aggregation: 'doc_count' as const, + filter: 'test.category: test', + }, + ], + 'A' + ); + const getIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(indicator); + expect(getIndicatorAggregation.execute('_metric')).toMatchSnapshot(); + }); + + it('should generate an aggregation for std_deviation', () => { + const indicator = createTimesliceMetricIndicator( + [ + { + name: 'A', + aggregation: 'std_deviation' as const, + field: 'test.field', + }, + ], + 'A' + ); + const getIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(indicator); + expect(getIndicatorAggregation.execute('_metric')).toMatchSnapshot(); + }); + + it('should generate an aggregation for percentile', () => { + const indicator = createTimesliceMetricIndicator( + [ + { + name: 'A', + aggregation: 'percentile' as const, + field: 'test.field', + percentile: 97, + }, + ], + 'A' + ); + const getIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(indicator); + expect(getIndicatorAggregation.execute('_metric')).toMatchSnapshot(); + }); + + it('should generate an aggregation for last_value', () => { + const indicator = createTimesliceMetricIndicator( + [ + { + name: 'A', + aggregation: 'last_value' as const, + field: 'test.field', + }, + ], + 'A' + ); + const getIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(indicator); + expect(getIndicatorAggregation.execute('_metric')).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.ts b/x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.ts new file mode 100644 index 0000000000000..e715038e324f0 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/aggregations/get_timeslice_metric_indicator_aggregation.ts @@ -0,0 +1,130 @@ +/* + * 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 { TimesliceMetricIndicator, timesliceMetricMetricDef } from '@kbn/slo-schema'; +import * as t from 'io-ts'; +import { assertNever } from '@kbn/std'; + +import { getElastichsearchQueryOrThrow } from '../transform_generators'; + +type TimesliceMetricDef = TimesliceMetricIndicator['params']['metric']; +type TimesliceMetricMetricDef = t.TypeOf; + +export class GetTimesliceMetricIndicatorAggregation { + constructor(private indicator: TimesliceMetricIndicator) {} + + private buildAggregation(metric: TimesliceMetricMetricDef) { + const { aggregation } = metric; + switch (aggregation) { + case 'doc_count': + return {}; + case 'std_deviation': + return { + extended_stats: { field: metric.field }, + }; + case 'percentile': + if (metric.percentile == null) { + throw new Error('You must provide a percentile value for percentile aggregations.'); + } + return { + percentiles: { + field: metric.field, + percents: [metric.percentile], + keyed: true, + }, + }; + case 'last_value': + return { + top_metrics: { + metrics: { field: metric.field }, + sort: { [this.indicator.params.timestampField]: 'desc' }, + }, + }; + case 'avg': + case 'max': + case 'min': + case 'sum': + case 'cardinality': + if (metric.field == null) { + throw new Error('You must provide a field for basic metric aggregations.'); + } + return { + [aggregation]: { field: metric.field }, + }; + default: + assertNever(aggregation); + } + } + + private buildBucketPath(prefix: string, metric: TimesliceMetricMetricDef) { + const { aggregation } = metric; + switch (aggregation) { + case 'doc_count': + return `${prefix}>_count`; + case 'std_deviation': + return `${prefix}>metric[std_deviation]`; + case 'percentile': + return `${prefix}>metric[${metric.percentile}]`; + case 'last_value': + return `${prefix}>metric[${metric.field}]`; + case 'avg': + case 'max': + case 'min': + case 'sum': + case 'cardinality': + return `${prefix}>metric`; + default: + assertNever(aggregation); + } + } + + private buildMetricAggregations(metricDef: TimesliceMetricDef) { + return metricDef.metrics.reduce((acc, metric) => { + const filter = metric.filter + ? getElastichsearchQueryOrThrow(metric.filter) + : { match_all: {} }; + const aggs = { metric: this.buildAggregation(metric) }; + return { + ...acc, + [`_${metric.name}`]: { + filter, + ...(metric.aggregation !== 'doc_count' ? { aggs } : {}), + }, + }; + }, {}); + } + + private convertEquationToPainless(bucketsPath: Record, equation: string) { + const workingEquation = equation || Object.keys(bucketsPath).join(' + '); + return Object.keys(bucketsPath).reduce((acc, key) => { + return acc.replaceAll(key, `params.${key}`); + }, workingEquation); + } + + private buildMetricEquation(definition: TimesliceMetricDef) { + const bucketsPath = definition.metrics.reduce( + (acc, metric) => ({ ...acc, [metric.name]: this.buildBucketPath(`_${metric.name}`, metric) }), + {} + ); + return { + bucket_script: { + buckets_path: bucketsPath, + script: { + source: this.convertEquationToPainless(bucketsPath, definition.equation), + lang: 'painless', + }, + }, + }; + } + + public execute(aggregationKey: string) { + return { + ...this.buildMetricAggregations(this.indicator.params.metric), + [aggregationKey]: this.buildMetricEquation(this.indicator.params.metric), + }; + } +} diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/index.ts b/x-pack/plugins/observability/server/services/slo/aggregations/index.ts index b814152a4fcd5..6df05b4b2eac5 100644 --- a/x-pack/plugins/observability/server/services/slo/aggregations/index.ts +++ b/x-pack/plugins/observability/server/services/slo/aggregations/index.ts @@ -7,3 +7,4 @@ export * from './get_histogram_indicator_aggregation'; export * from './get_custom_metric_indicator_aggregation'; +export * from './get_timeslice_metric_indicator_aggregation'; diff --git a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts index 494c54cd65741..e423a0441f9d5 100644 --- a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts +++ b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts @@ -6,7 +6,13 @@ */ import { SavedObject } from '@kbn/core-saved-objects-server'; -import { ALL_VALUE, CreateSLOParams, HistogramIndicator, sloSchema } from '@kbn/slo-schema'; +import { + ALL_VALUE, + CreateSLOParams, + HistogramIndicator, + sloSchema, + TimesliceMetricIndicator, +} from '@kbn/slo-schema'; import { cloneDeep } from 'lodash'; import { v1 as uuidv1 } from 'uuid'; import { @@ -90,6 +96,25 @@ export const createMetricCustomIndicator = ( }, }); +export const createTimesliceMetricIndicator = ( + metrics: TimesliceMetricIndicator['params']['metric']['metrics'] = [], + equation: TimesliceMetricIndicator['params']['metric']['equation'] = '', + queryFilter = '' +): TimesliceMetricIndicator => ({ + type: 'sli.metric.timeslice', + params: { + index: 'test-*', + timestampField: '@timestamp', + filter: queryFilter, + metric: { + metrics, + equation, + threshold: 100, + comparator: 'GTE', + }, + }, +}); + export const createHistogramIndicator = ( params: Partial = {} ): HistogramIndicator => ({ diff --git a/x-pack/plugins/observability/server/services/slo/get_preview_data.ts b/x-pack/plugins/observability/server/services/slo/get_preview_data.ts index 98f07e1f8ed5e..2fd80c09d3875 100644 --- a/x-pack/plugins/observability/server/services/slo/get_preview_data.ts +++ b/x-pack/plugins/observability/server/services/slo/get_preview_data.ts @@ -15,6 +15,7 @@ import { HistogramIndicator, KQLCustomIndicator, MetricCustomIndicator, + TimesliceMetricIndicator, } from '@kbn/slo-schema'; import { assertNever } from '@kbn/std'; import { APMTransactionDurationIndicator } from '../../domain/models'; @@ -23,6 +24,7 @@ import { InvalidQueryError } from '../../errors'; import { GetCustomMetricIndicatorAggregation, GetHistogramIndicatorAggregation, + GetTimesliceMetricIndicatorAggregation, } from './aggregations'; export class GetPreviewData { @@ -55,6 +57,7 @@ export class GetPreviewData { const result = await this.esClient.search({ index: indicator.params.index, + size: 0, query: { bool: { filter: [ @@ -130,6 +133,7 @@ export class GetPreviewData { const result = await this.esClient.search({ index: indicator.params.index, + size: 0, query: { bool: { filter: [ @@ -186,6 +190,7 @@ export class GetPreviewData { const timestampField = indicator.params.timestampField; const options = { index: indicator.params.index, + size: 0, query: { bool: { filter: [{ range: { [timestampField]: { gte: 'now-60m' } } }, filterQuery], @@ -228,6 +233,7 @@ export class GetPreviewData { const getCustomMetricIndicatorAggregation = new GetCustomMetricIndicatorAggregation(indicator); const result = await this.esClient.search({ index: indicator.params.index, + size: 0, query: { bool: { filter: [{ range: { [timestampField]: { gte: 'now-60m' } } }, filterQuery], @@ -261,6 +267,42 @@ export class GetPreviewData { })); } + private async getTimesliceMetricPreviewData( + indicator: TimesliceMetricIndicator + ): Promise { + const timestampField = indicator.params.timestampField; + const filterQuery = getElastichsearchQueryOrThrow(indicator.params.filter); + const getCustomMetricIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation( + indicator + ); + const result = await this.esClient.search({ + index: indicator.params.index, + size: 0, + query: { + bool: { + filter: [{ range: { [timestampField]: { gte: 'now-60m' } } }, filterQuery], + }, + }, + aggs: { + perMinute: { + date_histogram: { + field: timestampField, + fixed_interval: '1m', + }, + aggs: { + ...getCustomMetricIndicatorAggregation.execute('metric'), + }, + }, + }, + }); + + // @ts-ignore buckets is not improperly typed + return result.aggregations?.perMinute.buckets.map((bucket) => ({ + date: bucket.key_as_string, + sliValue: !!bucket.metric ? bucket.metric.value : null, + })); + } + private async getCustomKQLPreviewData( indicator: KQLCustomIndicator ): Promise { @@ -270,6 +312,7 @@ export class GetPreviewData { const timestampField = indicator.params.timestampField; const result = await this.esClient.search({ index: indicator.params.index, + size: 0, query: { bool: { filter: [{ range: { [timestampField]: { gte: 'now-60m' } } }, filterQuery], @@ -313,6 +356,8 @@ export class GetPreviewData { return this.getHistogramPreviewData(params.indicator); case 'sli.metric.custom': return this.getCustomMetricPreviewData(params.indicator); + case 'sli.metric.timeslice': + return this.getTimesliceMetricPreviewData(params.indicator); default: assertNever(type); } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/timeslice_metric.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/timeslice_metric.test.ts.snap new file mode 100644 index 0000000000000..e2698ba3e1793 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/timeslice_metric.test.ts.snap @@ -0,0 +1,697 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Timeslice Metric Transform Generator filters the source using the kql query 1`] = ` +Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-7d/d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + ], + }, +} +`; + +exports[`Timeslice Metric Transform Generator returns the expected transform params for timeslices slo 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 2, + }, + "description": "Rolled-up SLI data for SLO: irrelevant", + "dest": Object { + "index": ".slo-observability.sli-v2", + "pipeline": ".slo-observability.sli.pipeline", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "_A": Object { + "aggs": Object { + "metric": Object { + "avg": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + }, + "_B": Object { + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + }, + "_C": Object { + "aggs": Object { + "metric": Object { + "top_metrics": Object { + "metrics": Object { + "field": "test.field", + }, + "sort": Object { + "@timestamp": "desc", + }, + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_D": Object { + "aggs": Object { + "metric": Object { + "extended_stats": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_E": Object { + "aggs": Object { + "metric": Object { + "percentiles": Object { + "field": "test.field", + "keyed": true, + "percents": Array [ + 97, + ], + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>metric", + "B": "_B>_count", + "C": "_C>metric[test.field]", + "D": "_D>metric[std_deviation]", + "E": "_E>metric[97]", + }, + "script": Object { + "lang": "painless", + "source": "(params.A + params.B + params.C + params.D + params.E) / params.B", + }, + }, + }, + "slo.denominator": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1", + }, + }, + "slo.isGoodSlice": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "slo.numerator>value", + }, + "script": "params.goodEvents == 1 ? 1 : 0", + }, + }, + "slo.numerator": Object { + "bucket_script": Object { + "buckets_path": Object { + "value": "_metric>value", + }, + "script": Object { + "params": Object { + "threshold": 100, + }, + "source": "params.value >= params.threshold ? 1 : 0", + }, + }, + }, + }, + "group_by": Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "2m", + }, + }, + "slo.budgetingMethod": Object { + "terms": Object { + "field": "slo.budgetingMethod", + }, + }, + "slo.description": Object { + "terms": Object { + "field": "slo.description", + }, + }, + "slo.groupBy": Object { + "terms": Object { + "field": "slo.groupBy", + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.indicator.type": Object { + "terms": Object { + "field": "slo.indicator.type", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.name": Object { + "terms": Object { + "field": "slo.name", + }, + }, + "slo.objective.sliceDurationInSeconds": Object { + "terms": Object { + "field": "slo.objective.sliceDurationInSeconds", + }, + }, + "slo.objective.target": Object { + "terms": Object { + "field": "slo.objective.target", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "slo.tags": Object { + "terms": Object { + "field": "slo.tags", + }, + }, + "slo.timeWindow.duration": Object { + "terms": Object { + "field": "slo.timeWindow.duration", + }, + }, + "slo.timeWindow.type": Object { + "terms": Object { + "field": "slo.timeWindow.type", + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": "test-*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-7d/d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + ], + }, + }, + "runtime_mappings": Object { + "slo.budgetingMethod": Object { + "script": Object { + "source": "emit('timeslices')", + }, + "type": "keyword", + }, + "slo.description": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo.groupBy": Object { + "script": Object { + "source": "emit('*')", + }, + "type": "keyword", + }, + "slo.id": Object { + "script": Object { + "source": Any, + }, + "type": "keyword", + }, + "slo.indicator.type": Object { + "script": Object { + "source": "emit('sli.metric.timeslice')", + }, + "type": "keyword", + }, + "slo.instanceId": Object { + "script": Object { + "source": "emit('*')", + }, + "type": "keyword", + }, + "slo.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo.objective.sliceDurationInSeconds": Object { + "script": Object { + "source": "emit(120)", + }, + "type": "long", + }, + "slo.objective.target": Object { + "script": Object { + "source": "emit(0.98)", + }, + "type": "double", + }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, + "slo.tags": Object { + "script": Object { + "source": "emit('critical,k8s')", + }, + "type": "keyword", + }, + "slo.timeWindow.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo.timeWindow.type": Object { + "script": Object { + "source": "emit('rolling')", + }, + "type": "keyword", + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "1m", + "field": "@timestamp", + }, + }, + "transform_id": Any, +} +`; + +exports[`Timeslice Metric Transform Generator returns the expected transform params with every specified indicator params 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 2, + }, + "description": "Rolled-up SLI data for SLO: irrelevant", + "dest": Object { + "index": ".slo-observability.sli-v2", + "pipeline": ".slo-observability.sli.pipeline", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "_A": Object { + "aggs": Object { + "metric": Object { + "avg": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + }, + "_B": Object { + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + }, + "_C": Object { + "aggs": Object { + "metric": Object { + "top_metrics": Object { + "metrics": Object { + "field": "test.field", + }, + "sort": Object { + "@timestamp": "desc", + }, + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_D": Object { + "aggs": Object { + "metric": Object { + "extended_stats": Object { + "field": "test.field", + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_E": Object { + "aggs": Object { + "metric": Object { + "percentiles": Object { + "field": "test.field", + "keyed": true, + "percents": Array [ + 97, + ], + }, + }, + }, + "filter": Object { + "match_all": Object {}, + }, + }, + "_metric": Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_A>metric", + "B": "_B>_count", + "C": "_C>metric[test.field]", + "D": "_D>metric[std_deviation]", + "E": "_E>metric[97]", + }, + "script": Object { + "lang": "painless", + "source": "(params.A + params.B + params.C + params.D + params.E) / params.B", + }, + }, + }, + "slo.denominator": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1", + }, + }, + "slo.isGoodSlice": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "slo.numerator>value", + }, + "script": "params.goodEvents == 1 ? 1 : 0", + }, + }, + "slo.numerator": Object { + "bucket_script": Object { + "buckets_path": Object { + "value": "_metric>value", + }, + "script": Object { + "params": Object { + "threshold": 100, + }, + "source": "params.value >= params.threshold ? 1 : 0", + }, + }, + }, + }, + "group_by": Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "2m", + }, + }, + "slo.budgetingMethod": Object { + "terms": Object { + "field": "slo.budgetingMethod", + }, + }, + "slo.description": Object { + "terms": Object { + "field": "slo.description", + }, + }, + "slo.groupBy": Object { + "terms": Object { + "field": "slo.groupBy", + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.indicator.type": Object { + "terms": Object { + "field": "slo.indicator.type", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.name": Object { + "terms": Object { + "field": "slo.name", + }, + }, + "slo.objective.sliceDurationInSeconds": Object { + "terms": Object { + "field": "slo.objective.sliceDurationInSeconds", + }, + }, + "slo.objective.target": Object { + "terms": Object { + "field": "slo.objective.target", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "slo.tags": Object { + "terms": Object { + "field": "slo.tags", + }, + }, + "slo.timeWindow.duration": Object { + "terms": Object { + "field": "slo.timeWindow.duration", + }, + }, + "slo.timeWindow.type": Object { + "terms": Object { + "field": "slo.timeWindow.type", + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": "test-*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-7d/d", + }, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "test.category": "test", + }, + }, + ], + }, + }, + ], + }, + }, + "runtime_mappings": Object { + "slo.budgetingMethod": Object { + "script": Object { + "source": "emit('timeslices')", + }, + "type": "keyword", + }, + "slo.description": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo.groupBy": Object { + "script": Object { + "source": "emit('*')", + }, + "type": "keyword", + }, + "slo.id": Object { + "script": Object { + "source": Any, + }, + "type": "keyword", + }, + "slo.indicator.type": Object { + "script": Object { + "source": "emit('sli.metric.timeslice')", + }, + "type": "keyword", + }, + "slo.instanceId": Object { + "script": Object { + "source": "emit('*')", + }, + "type": "keyword", + }, + "slo.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo.objective.sliceDurationInSeconds": Object { + "script": Object { + "source": "emit(120)", + }, + "type": "long", + }, + "slo.objective.target": Object { + "script": Object { + "source": "emit(0.98)", + }, + "type": "double", + }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, + "slo.tags": Object { + "script": Object { + "source": "emit('critical,k8s')", + }, + "type": "keyword", + }, + "slo.timeWindow.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo.timeWindow.type": Object { + "script": Object { + "source": "emit('rolling')", + }, + "type": "keyword", + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "1m", + "field": "@timestamp", + }, + }, + "transform_id": Any, +} +`; diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts index 2b9337546d796..8bfaf865f340c 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts @@ -11,4 +11,5 @@ export * from './apm_transaction_duration'; export * from './kql_custom'; export * from './metric_custom'; export * from './histogram'; +export * from './timeslice_metric'; export * from './common'; diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.test.ts new file mode 100644 index 0000000000000..aa21f7e0ceb0e --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.test.ts @@ -0,0 +1,178 @@ +/* + * 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 { + createTimesliceMetricIndicator, + createSLOWithTimeslicesBudgetingMethod, + createSLO, +} from '../fixtures/slo'; +import { TimesliceMetricTransformGenerator } from './timeslice_metric'; + +const generator = new TimesliceMetricTransformGenerator(); +const everythingIndicator = createTimesliceMetricIndicator( + [ + { name: 'A', aggregation: 'avg', field: 'test.field', filter: 'test.category: "test"' }, + { name: 'B', aggregation: 'doc_count', filter: 'test.category: "test"' }, + { name: 'C', aggregation: 'last_value', field: 'test.field' }, + { name: 'D', aggregation: 'std_deviation', field: 'test.field' }, + { name: 'E', aggregation: 'percentile', field: 'test.field', percentile: 97 }, + ], + '(A + B + C + D + E) / B', + 'test.category: "test"' +); + +describe('Timeslice Metric Transform Generator', () => { + describe('validation', () => { + it('throws when the budgeting method is occurrences', () => { + const anSLO = createSLO({ + indicator: createTimesliceMetricIndicator( + [{ name: 'A', aggregation: 'avg', field: 'test.field' }], + '(A / 200) + A' + ), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow( + 'The sli.metric.timeslice indicator MUST have a timeslice budgeting method.' + ); + }); + it('throws when the metric equation is invalid', () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: createTimesliceMetricIndicator( + [{ name: 'A', aggregation: 'avg', field: 'test.field' }], + '(a / 200) + A' + ), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow(/Invalid equation/); + }); + it('throws when the metric filter is invalid', () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: createTimesliceMetricIndicator( + [{ name: 'A', aggregation: 'avg', field: 'test.field', filter: 'test:' }], + '(A / 200) + A' + ), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow(/Invalid KQL: test:/); + }); + it('throws when the query_filter is invalid', () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: createTimesliceMetricIndicator( + [{ name: 'A', aggregation: 'avg', field: 'test.field', filter: 'test.category: "test"' }], + '(A / 200) + A', + 'test:' + ), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow(/Invalid KQL/); + }); + }); + + it('returns the expected transform params with every specified indicator params', async () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: everythingIndicator, + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform).toMatchSnapshot({ + transform_id: expect.any(String), + source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, + }); + expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`); + expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({ + script: { source: `emit('${anSLO.id}')` }, + }); + expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({ + script: { source: `emit(${anSLO.revision})` }, + }); + }); + + it('returns the expected transform params for timeslices slo', async () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: everythingIndicator, + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform).toMatchSnapshot({ + transform_id: expect.any(String), + source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, + }); + }); + + it('filters the source using the kql query', async () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: everythingIndicator, + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.source.query).toMatchSnapshot(); + }); + + it('uses the provided index', async () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: { + ...everythingIndicator, + params: { ...everythingIndicator.params, index: 'my-own-index*' }, + }, + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.source.index).toBe('my-own-index*'); + }); + + it('uses the provided timestampField', async () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: { + ...everythingIndicator, + params: { ...everythingIndicator.params, timestampField: 'my-date-field' }, + }, + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.sync?.time?.field).toBe('my-date-field'); + // @ts-ignore + expect(transform.pivot?.group_by['@timestamp'].date_histogram.field).toBe('my-date-field'); + }); + + it('aggregates using the _metric equation', async () => { + const anSLO = createSLOWithTimeslicesBudgetingMethod({ + indicator: everythingIndicator, + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.pivot!.aggregations!._metric).toEqual({ + bucket_script: { + buckets_path: { + A: '_A>metric', + B: '_B>_count', + C: '_C>metric[test.field]', + D: '_D>metric[std_deviation]', + E: '_E>metric[97]', + }, + script: { + lang: 'painless', + source: '(params.A + params.B + params.C + params.D + params.E) / params.B', + }, + }, + }); + expect(transform.pivot!.aggregations!['slo.numerator']).toEqual({ + bucket_script: { + buckets_path: { + value: '_metric>value', + }, + script: { + params: { + threshold: 100, + }, + source: 'params.value >= params.threshold ? 1 : 0', + }, + }, + }); + expect(transform.pivot!.aggregations!['slo.denominator']).toEqual({ + bucket_script: { + buckets_path: {}, + script: '1', + }, + }); + }); +}); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.ts new file mode 100644 index 0000000000000..944b7d0622aad --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/timeslice_metric.ts @@ -0,0 +1,116 @@ +/* + * 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; +import { + timesliceMetricComparatorMapping, + TimesliceMetricIndicator, + timesliceMetricIndicatorSchema, + timeslicesBudgetingMethodSchema, +} from '@kbn/slo-schema'; + +import { InvalidTransformError } from '../../../errors'; +import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; +import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.'; +import { + SLO_DESTINATION_INDEX_NAME, + SLO_INGEST_PIPELINE_NAME, + getSLOTransformId, +} from '../../../assets/constants'; +import { SLO } from '../../../domain/models'; +import { GetTimesliceMetricIndicatorAggregation } from '../aggregations'; + +const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; + +export class TimesliceMetricTransformGenerator extends TransformGenerator { + public getTransformParams(slo: SLO): TransformPutTransformRequest { + if (!timesliceMetricIndicatorSchema.is(slo.indicator)) { + throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); + } + + return getSLOTransformTemplate( + this.buildTransformId(slo), + this.buildDescription(slo), + this.buildSource(slo, slo.indicator), + this.buildDestination(), + this.buildCommonGroupBy(slo, slo.indicator.params.timestampField), + this.buildAggregations(slo, slo.indicator), + this.buildSettings(slo, slo.indicator.params.timestampField) + ); + } + + private buildTransformId(slo: SLO): string { + return getSLOTransformId(slo.id, slo.revision); + } + + private buildSource(slo: SLO, indicator: TimesliceMetricIndicator) { + return { + index: parseIndex(indicator.params.index), + runtime_mappings: this.buildCommonRuntimeMappings(slo), + query: { + bool: { + filter: [ + { + range: { + [indicator.params.timestampField]: { + gte: `now-${slo.timeWindow.duration.format()}/d`, + }, + }, + }, + getElastichsearchQueryOrThrow(indicator.params.filter), + ], + }, + }, + }; + } + + private buildDestination() { + return { + pipeline: SLO_INGEST_PIPELINE_NAME, + index: SLO_DESTINATION_INDEX_NAME, + }; + } + + private buildAggregations(slo: SLO, indicator: TimesliceMetricIndicator) { + if (indicator.params.metric.equation.match(INVALID_EQUATION_REGEX)) { + throw new Error(`Invalid equation: ${indicator.params.metric.equation}`); + } + + if (!timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) { + throw new Error('The sli.metric.timeslice indicator MUST have a timeslice budgeting method.'); + } + + const getIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(indicator); + const comparator = timesliceMetricComparatorMapping[indicator.params.metric.comparator]; + return { + ...getIndicatorAggregation.execute('_metric'), + 'slo.numerator': { + bucket_script: { + buckets_path: { value: '_metric>value' }, + script: { + source: `params.value ${comparator} params.threshold ? 1 : 0`, + params: { threshold: indicator.params.metric.threshold }, + }, + }, + }, + 'slo.denominator': { + bucket_script: { + buckets_path: {}, + script: '1', + }, + }, + 'slo.isGoodSlice': { + bucket_script: { + buckets_path: { + goodEvents: 'slo.numerator>value', + }, + script: `params.goodEvents == 1 ? 1 : 0`, + }, + }, + }; + } +} From c9a7a2790b634fc6cfdb3d1d3e61b8b989e9242e Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 00:19:58 +0100 Subject: [PATCH 10/49] skip flaky suite (#168342) --- .../public/management/cypress/e2e/artifacts/artifacts.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts index 012456d92db9e..3850409f05911 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts.cy.ts @@ -36,7 +36,8 @@ const yieldAppliedEndpointRevision = (): Cypress.Chainable => const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]); -describe('Artifact pages', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/168342 +describe.skip('Artifact pages', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; From db3fd03517a025f9b6191378b12b5b6507ba6c0c Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 00:22:21 +0100 Subject: [PATCH 11/49] skip flaky suite (#168534) --- .../test/functional_with_es_ssl/apps/cases/group1/view_case.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts index b163413ba9ba0..9391c9f1e77db 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts @@ -1154,7 +1154,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); }); - describe('customFields', () => { + // FLAKY: https://github.com/elastic/kibana/issues/168534 + describe.skip('customFields', () => { const customFields = [ { key: 'valid_key_1', From fbdb612719b6c631d64b64474b8b3812388e6116 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 00:24:08 +0100 Subject: [PATCH 12/49] skip flaky suite (#168985) --- .../test_suites/common/visualizations/group2/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/index.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/index.ts index f3abd6dccef91..4b7e3588669d1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/index.ts @@ -10,7 +10,8 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ loadTestFile, getPageObject }: FtrProviderContext) => { const svlCommonPage = getPageObject('svlCommonPage'); - describe('Visualizations - Group 2', function () { + // FLAKY: https://github.com/elastic/kibana/issues/168985 + describe.skip('Visualizations - Group 2', function () { before(async () => { await svlCommonPage.login(); }); From 08b91ab9af8ba5589dc9f70554c3c287b1c6e4e5 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 00:25:02 +0100 Subject: [PATCH 13/49] skip flaky suite (#169134) --- .../test_suites/observability/telemetry/telemetry_config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts index d803cf06b4c52..5d25fef27daa8 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts @@ -11,7 +11,8 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext) const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - describe('/api/telemetry/v2/config API Telemetry config', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169134 + describe.skip('/api/telemetry/v2/config API Telemetry config', () => { const baseConfig = { allowChangingOptInStatus: false, optIn: true, From 7f9afeb0de988e4f760a1d25cf283413841af8ba Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 00:26:46 +0100 Subject: [PATCH 14/49] skip flaky suite (#169154) --- .../cypress/e2e/entity_analytics/enrichments.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts index 0c5662146b810..2bc04ec68c332 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts @@ -50,7 +50,8 @@ describe('Enrichment', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, }); describe('Custom query rule', () => { - describe('from legacy risk scores', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169154 + describe.skip('from legacy risk scores', () => { beforeEach(() => { disableExpandableFlyout(); cy.task('esArchiverLoad', { archiveName: 'risk_hosts' }); From 514d99e05a9698174741884bd99e0029d25e2619 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 00:32:45 +0100 Subject: [PATCH 15/49] skip flaky suite (#167056) --- .../security_and_spaces/prebuilt_rules/fleet_integration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts index 1433cb7cac2ff..0683868ae3413 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts @@ -22,7 +22,8 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - describe('install_prebuilt_rules_from_real_package', () => { + // FLAKY: https://github.com/elastic/kibana/issues/167056 + describe.skip('install_prebuilt_rules_from_real_package', () => { beforeEach(async () => { await deletePrebuiltRulesFleetPackage(supertest); await deleteAllRules(supertest, log); From 267fdb1ace81fc68a4435f2906da3f52f6252808 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 17 Oct 2023 20:31:15 -0500 Subject: [PATCH 16/49] [ci] Temporarily move osquery tests back to on_merge_unsupported pipeline (#169189) These tests are currently failing - https://buildkite.com/elastic/kibana-on-merge/builds/36968 https://buildkite.com/elastic/kibana-on-merge/builds/36969 We can move these back to the main pipeline after tests are working and [junit reports are available](https://github.com/elastic/kibana/issues/169018). --- .buildkite/pipelines/on_merge.yml | 12 ------ .../pipelines/on_merge_unsupported_ftrs.yml | 12 ++++++ .buildkite/pipelines/pull_request/base.yml | 40 ------------------- .../pull_request/osquery_cypress.yml | 40 +++++++++++++++++++ .../pipelines/pull_request/pipeline.ts | 8 ++++ 5 files changed, 60 insertions(+), 52 deletions(-) create mode 100644 .buildkite/pipelines/pull_request/osquery_cypress.yml diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 815e4d9adb5e2..5c587545897f5 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -187,18 +187,6 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/functional/osquery_cypress.sh - label: 'Osquery Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 50 - parallelism: 6 - retry: - automatic: - - exit_status: '*' - limit: 1 - - command: '.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh' label: Trigger unsupported ftr tests timeout_in_minutes: 10 diff --git a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml index 6dee27db71659..904bed2b042ab 100644 --- a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml +++ b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml @@ -63,3 +63,15 @@ steps: limit: 3 - exit_status: '*' limit: 1 + + - command: .buildkite/scripts/steps/functional/osquery_cypress.sh + label: 'Osquery Cypress Tests' + agents: + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 50 + parallelism: 6 + retry: + automatic: + - exit_status: '*' + limit: 1 diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index 6437742d31c15..5213dfc0e4ab1 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -187,20 +187,6 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/functional/osquery_cypress.sh - label: 'Osquery Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 50 - parallelism: 6 - retry: - automatic: - - exit_status: '*' - limit: 1 - artifact_paths: - - 'target/kibana-osquery/**/*' - - command: .buildkite/scripts/steps/functional/security_solution_burn.sh label: 'Security Solution Cypress tests, burning changed specs' agents: @@ -212,32 +198,6 @@ steps: automatic: false soft_fail: true - - command: .buildkite/scripts/steps/functional/osquery_cypress_burn.sh - label: 'Osquery Cypress Tests, burning changed specs' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 50 - soft_fail: true - retry: - automatic: false - artifact_paths: - - 'target/kibana-osquery/**/*' - - - command: .buildkite/scripts/steps/functional/security_serverless_osquery.sh - label: 'Serverless Osquery Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 50 - parallelism: 6 - retry: - automatic: - - exit_status: '*' - limit: 1 - artifact_paths: - - 'target/kibana-osquery/**/*' - # status_exception: Native role management is not enabled in this Elasticsearch instance # - command: .buildkite/scripts/steps/functional/security_serverless_defend_workflows.sh # label: 'Serverless Security Defend Workflows Cypress Tests' diff --git a/.buildkite/pipelines/pull_request/osquery_cypress.yml b/.buildkite/pipelines/pull_request/osquery_cypress.yml new file mode 100644 index 0000000000000..021fbe5f5e679 --- /dev/null +++ b/.buildkite/pipelines/pull_request/osquery_cypress.yml @@ -0,0 +1,40 @@ +steps: + - command: .buildkite/scripts/steps/functional/osquery_cypress.sh + label: 'Osquery Cypress Tests' + agents: + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 50 + parallelism: 6 + retry: + automatic: + - exit_status: '*' + limit: 1 + artifact_paths: + - "target/kibana-osquery/**/*" + + - command: .buildkite/scripts/steps/functional/security_serverless_osquery.sh + label: 'Serverless Osquery Cypress Tests' + agents: + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 50 + parallelism: 6 + retry: + automatic: + - exit_status: '*' + limit: 1 + artifact_paths: + - 'target/kibana-osquery/**/*' + + - command: .buildkite/scripts/steps/functional/osquery_cypress_burn.sh + label: 'Osquery Cypress Tests, burning changed specs' + agents: + queue: n2-4-spot + depends_on: build + timeout_in_minutes: 50 + soft_fail: true + retry: + automatic: false + artifact_paths: + - 'target/kibana-osquery/**/*' \ No newline at end of file diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index 7a7fa0f59b9c7..4d6cd774393e0 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -151,6 +151,14 @@ const uploadPipeline = (pipelineContent: string | object) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/webpack_bundle_analyzer.yml')); } + if ( + ((await doAnyChangesMatch([/^x-pack\/plugins\/osquery/, /^x-pack\/test\/osquery_cypress/])) || + GITHUB_PR_LABELS.includes('ci:all-cypress-suites')) && + !GITHUB_PR_LABELS.includes('ci:skip-cypress-osquery') + ) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/osquery_cypress.yml')); + } + if ( (await doAnyChangesMatch([ /\.docnav\.json$/, From 265ee371fd882a8a951644eeff7219a8f73856f9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 18 Oct 2023 00:52:50 -0400 Subject: [PATCH 17/49] [api-docs] 2023-10-18 Daily api_docs build (#169192) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/494 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/apm_data_access.mdx | 2 +- api_docs/asset_manager.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_data_migration.mdx | 2 +- api_docs/cloud_defend.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/content_management.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/elastic_assistant.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.devdocs.json | 4 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_annotation_listing.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.devdocs.json | 21 +- api_docs/fleet.mdx | 4 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- .../kbn_alerting_api_integration_helpers.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_analytics_shippers_gainsight.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mocks.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_content_editor.mdx | 2 +- ...tent_management_tabbed_table_list_view.mdx | 2 +- ...kbn_content_management_table_list_view.mdx | 2 +- ...ntent_management_table_list_view_table.mdx | 2 +- api_docs/kbn_content_management_utils.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_apps_server_internal.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_custom_branding_browser.mdx | 2 +- ..._core_custom_branding_browser_internal.mdx | 2 +- ...kbn_core_custom_branding_browser_mocks.mdx | 2 +- api_docs/kbn_core_custom_branding_common.mdx | 2 +- api_docs/kbn_core_custom_branding_server.mdx | 2 +- ...n_core_custom_branding_server_internal.mdx | 2 +- .../kbn_core_custom_branding_server_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.devdocs.json | 72 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- .../kbn_core_test_helpers_model_versions.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_core_user_settings_server.mdx | 2 +- ...kbn_core_user_settings_server_internal.mdx | 2 +- .../kbn_core_user_settings_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_custom_integrations.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_data_service.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_deeplinks_analytics.mdx | 2 +- api_docs/kbn_deeplinks_devtools.mdx | 2 +- api_docs/kbn_deeplinks_management.mdx | 2 +- api_docs/kbn_deeplinks_ml.mdx | 2 +- api_docs/kbn_deeplinks_observability.mdx | 2 +- api_docs/kbn_deeplinks_search.mdx | 2 +- api_docs/kbn_default_nav_analytics.mdx | 2 +- api_docs/kbn_default_nav_devtools.mdx | 2 +- api_docs/kbn_default_nav_management.mdx | 2 +- api_docs/kbn_default_nav_ml.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_discover_utils.mdx | 2 +- api_docs/kbn_doc_links.devdocs.json | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_elastic_assistant.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_event_annotation_common.mdx | 2 +- api_docs/kbn_event_annotation_components.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_field_utils.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_console_definitions.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_generate_csv_types.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_infra_forge.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_lens_embeddable_utils.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_management_cards_navigation.mdx | 2 +- .../kbn_management_settings_application.mdx | 2 +- ...ent_settings_components_field_category.mdx | 2 +- ...gement_settings_components_field_input.mdx | 2 +- ...nagement_settings_components_field_row.mdx | 2 +- ...bn_management_settings_components_form.mdx | 2 +- ...n_management_settings_field_definition.mdx | 2 +- .../kbn_management_settings_ids.devdocs.json | 15 - api_docs/kbn_management_settings_ids.mdx | 4 +- ...n_management_settings_section_registry.mdx | 2 +- api_docs/kbn_management_settings_types.mdx | 2 +- .../kbn_management_settings_utilities.mdx | 2 +- api_docs/kbn_management_storybook_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_maps_vector_tile_utils.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_anomaly_utils.mdx | 2 +- api_docs/kbn_ml_category_validator.mdx | 2 +- api_docs/kbn_ml_chi2test.mdx | 2 +- .../kbn_ml_data_frame_analytics_utils.mdx | 2 +- api_docs/kbn_ml_data_grid.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_date_utils.mdx | 2 +- api_docs/kbn_ml_error_utils.mdx | 2 +- api_docs/kbn_ml_in_memory_table.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_kibana_theme.mdx | 2 +- api_docs/kbn_ml_local_storage.mdx | 2 +- api_docs/kbn_ml_nested_property.mdx | 2 +- api_docs/kbn_ml_number_utils.mdx | 2 +- api_docs/kbn_ml_query_utils.mdx | 2 +- api_docs/kbn_ml_random_sampler_utils.mdx | 2 +- api_docs/kbn_ml_route_utils.mdx | 2 +- api_docs/kbn_ml_runtime_field_utils.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- api_docs/kbn_openapi_generator.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_profiling_utils.mdx | 2 +- api_docs/kbn_random_sampling.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_react_kibana_context_common.mdx | 2 +- api_docs/kbn_react_kibana_context_render.mdx | 2 +- api_docs/kbn_react_kibana_context_root.mdx | 2 +- api_docs/kbn_react_kibana_context_styled.mdx | 2 +- api_docs/kbn_react_kibana_context_theme.mdx | 2 +- api_docs/kbn_react_kibana_mount.mdx | 2 +- api_docs/kbn_repo_file_maps.mdx | 2 +- api_docs/kbn_repo_linter.mdx | 2 +- api_docs/kbn_repo_path.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_reporting_common.mdx | 2 +- api_docs/kbn_resizable_layout.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_rrule.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_search_api_panels.mdx | 2 +- api_docs/kbn_search_connectors.devdocs.json | 4 +- api_docs/kbn_search_connectors.mdx | 2 +- api_docs/kbn_search_response_warnings.mdx | 2 +- api_docs/kbn_security_solution_features.mdx | 2 +- api_docs/kbn_security_solution_navigation.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_grouping.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_serverless_common_settings.mdx | 2 +- .../kbn_serverless_observability_settings.mdx | 2 +- api_docs/kbn_serverless_project_switcher.mdx | 2 +- api_docs/kbn_serverless_search_settings.mdx | 2 +- api_docs/kbn_serverless_security_settings.mdx | 2 +- api_docs/kbn_serverless_storybook_config.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_chrome_navigation.mdx | 2 +- api_docs/kbn_shared_ux_file_context.mdx | 2 +- api_docs/kbn_shared_ux_file_image.mdx | 2 +- api_docs/kbn_shared_ux_file_image_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_picker.mdx | 2 +- api_docs/kbn_shared_ux_file_types.mdx | 2 +- api_docs/kbn_shared_ux_file_upload.mdx | 2 +- api_docs/kbn_shared_ux_file_util.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- .../kbn_shared_ux_page_no_data.devdocs.json | 20 +- api_docs/kbn_shared_ux_page_no_data.mdx | 4 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_prompt_not_found.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.devdocs.json | 1340 ++++++++++++++++- api_docs/kbn_slo_schema.mdx | 4 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_subscription_tracking.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_text_based_editor.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_unified_data_table.mdx | 2 +- api_docs/kbn_unified_doc_viewer.mdx | 2 +- api_docs/kbn_unified_field_list.mdx | 2 +- api_docs/kbn_url_state.mdx | 2 +- api_docs/kbn_use_tracked_promise.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_visualization_ui_components.mdx | 2 +- api_docs/kbn_xstate_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/links.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/log_explorer.mdx | 2 +- api_docs/logs_shared.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.devdocs.json | 15 + api_docs/maps_ems.mdx | 4 +- api_docs/metrics_data_access.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/no_data_page.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.devdocs.json | 448 +++++- api_docs/observability.mdx | 2 +- api_docs/observability_a_i_assistant.mdx | 2 +- api_docs/observability_log_explorer.mdx | 2 +- api_docs/observability_onboarding.mdx | 2 +- api_docs/observability_shared.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/painless_lab.mdx | 2 +- api_docs/plugin_directory.mdx | 16 +- api_docs/presentation_util.devdocs.json | 129 +- api_docs/presentation_util.mdx | 4 +- api_docs/profiling.devdocs.json | 4 +- api_docs/profiling.mdx | 2 +- api_docs/profiling_data_access.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/security_solution_ess.mdx | 2 +- api_docs/security_solution_serverless.mdx | 2 +- api_docs/serverless.mdx | 2 +- api_docs/serverless_observability.mdx | 2 +- api_docs/serverless_search.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/text_based_languages.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_doc_viewer.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/uptime.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.devdocs.json | 4 +- api_docs/visualizations.mdx | 2 +- 610 files changed, 2423 insertions(+), 875 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index e8d53a7a433ed..f598d299b7952 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index e8641d2346733..efdfe59fa3040 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 4fc83fdc3737d..74da23a1fa1b9 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index c1c6ba03ac363..02dc00b27ca13 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 07798b2d7d44c..8287d3919a991 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 8e21775b0d20b..d7b26fd019fce 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index a79aa72f869d7..2cff2d5c0849f 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 4033d8c8c4760..83c98cab152f4 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index f323de530edf3..47c8f43a041c2 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 085a895187864..b8c88c032ee77 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 28259ee380be9..f2fdc68160071 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index d4bf1e7a9e370..aee8df4a23f9a 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index a9a30caf1a447..7b8f4eae37dae 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 87b7551a39131..03d162c302217 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index bd261cb318035..e8c7b777c983e 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index ca9b201336552..a2366607958b7 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index a94173f5e5c00..75b7596ba5086 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index a1129f4e78245..8a3477a8dfacd 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index df4af236d4167..2488f2ac6532a 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index c10fcafd5449d..862602ee17c6a 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 7cceeeae87338..b31437b6180ad 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index e1e2e6f161749..e39c03f52d508 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 6dae7ddb65e9d..ab32de268551c 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index a1240cf855d3c..fce9c0d289758 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 2bf79e7f68ce3..d142b7e5ff89f 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index c378df00f6d58..dbf6667d76ddc 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index f96edd39eccef..fdac0998fc79d 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index f075eb62cdd6f..21383a76ece72 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index d5bbc3317608e..eb7b2fad1faf6 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 5cbdc172a2aa0..5f3de11555319 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index ba391b2c56639..5122172501e47 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 0d5461d79b79a..3bd8dfaf859b0 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index fbc91a3023b2b..8b9b3cdf5f69c 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a589030f4624e..fcaa5cdf99e7e 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 031af628232af..21aedd218aa4f 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 80bbfd1f0fe0c..6062eba281e76 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 6350dba94d9cb..e766717018974 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 0f66a0e731515..2cd91fb61456f 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index b3f95de80246f..90b235e6f81b7 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index d8bd9acd9204b..aeb1b2032f94d 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index b3a2c58b2c0fd..40a066617027f 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 71bbbf05e67ac..584cd3f07050c 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 7b62a57120c53..f6f428690d66e 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.devdocs.json b/api_docs/es_ui_shared.devdocs.json index 37cdf84e7503e..89ea258fe6962 100644 --- a/api_docs/es_ui_shared.devdocs.json +++ b/api_docs/es_ui_shared.devdocs.json @@ -527,7 +527,7 @@ "label": "SectionLoading", "description": [], "signature": [ - "({ inline, children, ...rest }: React.PropsWithChildren) => JSX.Element" + "({ inline, \"data-test-subj\": dataTestSubj, children, ...rest }: React.PropsWithChildren) => JSX.Element" ], "path": "src/plugins/es_ui_shared/public/components/section_loading/section_loading.tsx", "deprecated": false, @@ -538,7 +538,7 @@ "id": "def-public.SectionLoading.$1", "type": "CompoundType", "tags": [], - "label": "{ inline, children, ...rest }", + "label": "{\n inline,\n 'data-test-subj': dataTestSubj,\n children,\n ...rest\n}", "description": [], "signature": [ "React.PropsWithChildren" diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index e9b4556666441..c50ffd21b3ba9 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 5814fc1d33338..fe2b80985f59f 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index 17908b8f15b29..3c8ac928b35a7 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 3a7324323f21c..6655e6b39dcc2 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 2a62155db9b32..ba280037c0952 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index ec8c08805ba55..0154202e25245 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 18579b8249394..92548b77fc1fd 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 131b7e5bfd851..6deb1327a5ff6 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 734652e90107c..f8870390ed0ed 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 180dcc1332a96..6339f330f1e53 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index ad9e58cea41b3..a61cb31f6f61c 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 38a0810324684..bf5b4d05c4816 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 56abea0e5d685..3b1336e2c95bf 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 8ce94ac16fcee..6896cecd5e596 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 7a361f276babf..b78ff21638e5e 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index fa2dbd9822132..bb4cc8b27c60b 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 508ae3b853683..f6ef3a5b0bd1a 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 75aca7f07e7a0..644b16e3e441e 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 197c8b9e9bc26..8166a8dff084c 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 9605228ce6245..29ef7f49f7b88 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 99531d9ab062c..f30a4ebd3a939 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index bfca0d8647365..1d097578b94e4 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 22b0eb553905b..7eacea44c6840 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 31ab7e90df446..3f8ba3184b85a 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 6ef62450ad344..fd706a0787873 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -18961,15 +18961,30 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.FleetServerAgent.upgrade_details", + "type": "Object", + "tags": [], + "label": "upgrade_details", + "description": [ + "\nID of the API key the Elastic Agent must used to contact Fleet Server\n\nUpgrade state of the Elastic Agent" + ], + "signature": [ + "AgentUpgradeDetails", + " | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/agent.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.FleetServerAgent.access_api_key_id", "type": "string", "tags": [], "label": "access_api_key_id", - "description": [ - "\nID of the API key the Elastic Agent must used to contact Fleet Server" - ], + "description": [], "signature": [ "string | undefined" ], diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index e10ba0c2c95ab..95cb660c99011 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1208 | 3 | 1090 | 43 | +| 1209 | 3 | 1091 | 44 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 294043c0b4e0a..6eeaeb1623a3c 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 7ee2c22aea645..eb0e7ee5570b5 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index e201218f8b108..cb91f7074f8bd 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index bedcf9289117f..4e9e0efc05c79 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 4a16cc47b8562..b69e624f94a8b 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 34fa5b65248dd..df51448155f3a 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 28a86837a0703..c35679dcf4037 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 607f101785afa..5f48fad8636c5 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index b861084bc6f8d..afac3ddae64ca 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index f24d13ad3c1ba..94495a1b3d1cc 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 7f111b27796d2..00cf9cdb57229 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 21ce788f458c0..6ea3ee7fb2d6c 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index bdfb82934e148..493bc1ae62a51 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 17552883bb50f..c20ba5cddaed9 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index c10933a2e584c..9a7f486d2fc2e 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 37c648f26df25..0545defba3a2d 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index b45f523dcf075..600e80991fa15 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 1bea59f569db6..b02eacc402536 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index f1b840c8cf083..dd2f6f11ae076 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 4fcd800fa0890..b42ce7b6db699 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 8f785a129d754..1fd9e36381efa 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index c61cf4706338f..ca9d7649d4646 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index d58bc1e0b85df..6b7fa84fe56b3 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 125cee1ce451a..85259c64bfc9e 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index aa921476251dc..e19a70b35efb9 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 2c3f57d91dab8..1f6fa002fc1d8 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index f6c6dc1045781..083aecb32438f 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index e0d4804415547..de84c1293db60 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 301d820ee903e..e2bd1f02bc8e8 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index d7d64e7666a9c..b53e1cb30b776 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index bb1ae0ffd5a38..3df9ce29183f1 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 3456b88d4f68c..39dee7afff548 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index ed7a7345362ea..89d83f95d1cec 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 857415c537340..bd1508e1eab5f 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 30540e5bad733..8df87e6da1ad8 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 69be9d21ab639..992b7e7245132 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index f506f43cc7370..cbb4c44ad2ad7 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx index 605001160837f..c5cab8c0fbe06 100644 --- a/api_docs/kbn_code_editor_mocks.mdx +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mocks title: "@kbn/code-editor-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] --- import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 28eb5f07578a9..73d82f26c4c18 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 5edc888e9d765..e0797539f5634 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index a7d3218217ef1..29c4e37f18c4b 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 792061cd1c992..d25f2d384f919 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 91133fcbce883..a7564002e2f80 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index 57a5dc416bebd..75996f4483896 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index cc87df2d73112..66ac0d1614cc9 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index 62e3f75d20918..306267a6abada 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index c80de7f136a3d..11155b30e699f 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 5676843336ff6..91eb8b10f3a5a 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 71514053b4419..df656ecd61406 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index f373c880d7ea5..ec53ba8c11877 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 56c92d4b375f0..41d955623cb24 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 590486afd0b1e..9d691f27ce8e1 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 523eea43000ef..aac21b064dc5c 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index b362faa3d4596..b7facaca6d8b2 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index cdb6efd28345d..a3465ade3a3d9 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index c514789c3612d..8a552ba93fa44 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 8a3f93eaa59ec..3594a96e2f42b 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 4813860f00f82..df550f5ea06ba 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index c8f892aa52599..ec7aa6f619889 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index eca63186b2634..48474b6639288 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index d2a3372d95c3e..de80610f095b2 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 9e816e4abe739..a2fb258b8ecf0 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 5ffa0c34f7260..1f1a4a10877c4 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index f4ffec00592b0..26574c1ac8b8e 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 257222a9dda68..927f7277a4501 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 436d38caefead..55909f038034f 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 211bbdbc8039b..7c95419ad4f7a 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index adbe60b8d8e71..1d5474cc18045 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 3ba5de16dc314..20859d4f6d1da 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index a9e4d04eb2c8b..b1cfb51c5948d 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index cf9affc14ad04..673f164bbff2f 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index f26b7a1c36060..239207b3fe74c 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index 76964b6badf6a..55d77a39b57c6 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 2a6f36235af7b..7440d94a14bf4 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index e24461f250ad1..fb56e91e952a7 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index baca4ef57edf6..ed28c0d11a862 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index ce67d8ade4042..b6e209392f286 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index 8b9bb43e8b419..f9b96fa733557 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 6e36721e6a741..1caa989923609 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 03b2a4c824ab3..657907431835f 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 52d72fd33b80e..0e01f49afc728 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index ff79fc108cfc8..40319fb3b9d71 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 63bd4fa4a0b86..c972253a1d766 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index b03523b67392f..836e2029f733f 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index ef26be8adf191..850e80b04028d 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 79d8624097c8d..ffdd0b653c5c3 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index f1574aa817481..457f1744e4a46 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 31078d04b6cb0..cd8ddb2c7fd9e 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 11b17a2f10728..c89874205e4da 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index b95bf2a78edb5..640e917ab94f0 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 18aa165132769..7ebf74b577c8f 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 7b4d0093c0a16..662163b17bd61 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 73b8ac4a3686d..a586e467e5866 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 5c2cff8503113..46e6055acad82 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 1cbdf72a18e5d..f8a4673f8ac76 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 6e0d9e9b46d19..11c8d6a399862 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index bc1f09b82e11b..6704ea91656ec 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 879d622e5b6fa..5f890c4d06972 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index a842e3ea69246..ec04f75c8aba8 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 88e0567d1d09d..8b99f3c87ece3 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 26c773de8c2d3..ae8a69fab1d8c 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index b7f505748ec07..c14187750d19b 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 0183854b9ac49..af850f7922072 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index aa9569680a561..cf518d1280327 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index c12a97bcd3a13..20ac5ecbe9679 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 891801aee23b4..a0ce404c4c0fe 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 6aa0f2e5d54cd..4e5a04a6045d5 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 0734c13b6f495..1f6fe906524af 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 47e399d99f048..080ad66e8f2e6 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index d2e55a61a6cb3..515e4eb650c33 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index e4fad9d599773..b2bdfb6764663 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 2d401393b92b7..11b9583f114d5 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 66f6027212e58..f5ad6a24e986e 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 9712e2de2b2f1..e34c12e8adf7b 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 8f84f1d22417b..6a8ae9925b1bd 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 70a77351b3ab2..30258151919d7 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index d88d8a1fd7250..abeeee5830cd5 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -3815,18 +3815,6 @@ "plugin": "crossClusterReplication", "path": "x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts" - }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts" - }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_status.ts" @@ -4859,10 +4847,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule_types.test.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/__mocks__/server.ts" @@ -6269,10 +6253,6 @@ "plugin": "crossClusterReplication", "path": "x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" @@ -7173,10 +7153,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/__mocks__/server.ts" @@ -8279,10 +8255,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/update_rule.test.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/__mocks__/server.ts" @@ -8629,10 +8601,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/bulk_enable_rules.test.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/__mocks__/server.ts" @@ -9191,10 +9159,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/delete_rule.test.ts" }, - { - "plugin": "ecsDataQualityDashboard", - "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" - }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/__mocks__/server.ts" @@ -13229,6 +13193,18 @@ "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts" }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts" + }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts" + }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts" + }, { "plugin": "logsShared", "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" @@ -13653,6 +13629,10 @@ "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts" }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" + }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts" @@ -13957,6 +13937,10 @@ "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts" }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" + }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts" @@ -14469,6 +14453,10 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/import.ts" }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_unallowed_field_values.ts" + }, { "plugin": "logsShared", "path": "x-pack/plugins/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" @@ -14861,6 +14849,10 @@ "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts" }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" + }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts" @@ -14992,6 +14984,10 @@ { "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts" + }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" } ], "returnComment": [], @@ -15217,6 +15213,10 @@ "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts" }, + { + "plugin": "ecsDataQualityDashboard", + "path": "x-pack/plugins/ecs_data_quality_dashboard/server/__mocks__/server.ts" + }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 784d53314f9ae..a11f782478db8 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index c779d59a4f22f..e5cec103da6f0 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 08946f1a35c9e..1bf6538f0cbc5 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index c319f22f8188b..350905f033738 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index e80328a76f354..402bb38aa2738 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index cc0f4898db7d0..4584c70c43048 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index d3f000f64098f..701c1ec779aca 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index e0ef09c6faec5..298c7da21ab88 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index a7417dca1aaa1..ce20518fc04db 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 3e478d6f4b53b..464b6469b51f4 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index bf7fd3ad46436..38b4884d99558 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 618b28c43e99c..ad8f8724d8055 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 2bd868e8f8311..6574f66ea8f1d 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 92639f54ab8f3..e93d8bf80d46b 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 32101dc9c54aa..c3625e2986d56 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index dfb374ec51d07..1a78a8509f0b6 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 210e8423bb0e6..814375ab0295b 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 03cf1bb25a339..2ae4ab4762006 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index aee479ea68758..256f03d508c00 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index ff6c63e72cb04..0c5c6ea1c2013 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index de421df3dd8a7..3c32d8654cda8 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 60a4a04ea56b0..42064e79efd02 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index b0a0207307e9a..0406b0483d4e5 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 8ac0e295d3010..ff772adc17989 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 1af25d7acbd1f..b661f7823b10e 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 98ae0b69dd6ce..6a966c5cc8446 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index ed033f477fc7d..51eeb32031b98 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 9d5ad1f479b10..975cc027213f3 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index bd67e074c8327..151f94dcb9e69 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 15d358cfc2952..319d87c359ee5 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index e6fe2e8ecb17d..540b22a0c93bd 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 2f7ec4415c6c7..ae67e8b45e10e 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index beedf8d4e58f4..310141dded650 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 6367a14852e11..243f9cdafc3c3 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index b8bb68a2cfd9c..4f2563bab174d 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 66111a0f7d657..a21591c04197b 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 838daf6d8e1d0..5880df1e788c2 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index fe89389e84e94..571ed3432c2dc 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index aa7575c38a2ad..1190111cca9c0 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 5720a448ebb24..42198e233860d 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 56a260ff8ebcd..80f08df6bbb46 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index bad727a256176..a2293deeda203 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index e54cb36eb21bc..c89c781f12213 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 72ae780f7db92..039d838d0c2ea 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index a0a41359baad4..e0d7426e3d74e 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 6eb8522aafbf2..1e7825ce8078e 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index f35bc7f40cf52..8076e2f0a36ff 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 1f3eda2b9f1b3..fa0d2d074a5e4 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 02637ce1468c8..1a371c8ec1903 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index cb99f7a5dde39..8b9ecad86b3ad 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 94469755b93b0..84ee2004c2a08 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 8591454a5f954..506c73cf096e8 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 1578ff9692585..b4a82df25a195 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index d2c740366d231..3110041812f7f 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 56bc9e2f1c505..98a5e7a84c76e 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index ad8e13a8a9132..52040246662ab 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 0e1271923ccfd..e22bfc4ecbaa2 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 30382ecaab6e4..11ecd6e43744a 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index cf5c89f57edf2..7a884888fab7d 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index d34b7c64e736a..8a6d3004d3536 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 115eea47a436c..2247ebd114cc3 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 79845354a5f19..69f38ebde7b95 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index ade68aee40e81..125534579a6b6 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index d234c9506c779..a9747a6cedd65 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index e51f2cfe24ba8..759f90a84ae3b 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index cd8255edd256c..4dd7efdab47b0 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 965bbab2e503e..e86cca75f4e74 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 5329336b9544b..bfa9f231ff89d 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 5532f33ab2e4e..8f458a1241998 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index 6b29d8a99554e..57851b7d2a29b 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index 03770ea48bacd..3bebca901cb3b 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 6a2c4514eb72b..1633bf97e609d 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index d6f23a203c566..5b7c2a28d9f60 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index ce2024bb5248f..9758a42a8bf6d 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index c965febf4fb07..203acc81167ed 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index a8d9bb4f3ac0c..68c710ee4d260 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index ee728910e42f9..45cb836ecd635 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index dbe2bc10d74ce..64410905062e0 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 5a25486e52518..04d83683e797b 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 9fe6627c325cd..c22e8d5c8721c 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 838fcb1aae356..4f917234096f4 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index cc2c366472b93..b6c6b9c3d0699 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index ecdc9f63d3f23..29dfd0acc1cdb 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index b67f94f44acd6..ceff5594fb719 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index e6a92a1ee0e10..d9ae19c2b9f08 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 121fa189456ca..22f7d233d1fe3 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_internal.mdx b/api_docs/kbn_core_user_settings_server_internal.mdx index fd8537dc850bc..803005652a87f 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-internal'] --- import kbnCoreUserSettingsServerInternalObj from './kbn_core_user_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 4922f6c1ac14a..62ecfa529b8ff 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 00ac9bcfa92b9..289fa82165a64 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index dc7836b73f9e1..eeeb48b8a2d69 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 8f43e18674892..151cc49157c9b 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index c6d5cc7e2a2f3..e7a3a2dbcc9e5 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index c8608795376d4..03a71fd9484f3 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 052febf795a11..1308b7455d408 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 439e3851d53e6..4a4998f3ad48c 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index 79f2b1056fd0a..d72a545ea10c3 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 8c0a413406b99..f863bcfd7566a 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 1395ae3325cab..67a843c745d28 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index 0e3a433971632..0d213c503fa85 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 4a49fb66d5417..59d0e76084db4 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index 9ae22cb5eb77b..789b32261a3dc 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index a89ef9ace78ab..f997d6b329962 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 72711c81df54d..23089a8c2cdfd 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index 2a493255e4aea..bde41cb285650 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 208bc76cf1809..21b0e0cfd841d 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index c446ea3a3da6f..04025ae09c643 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 8b3593fa76a15..65c1b9041d81a 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 69a6c8203e932..3bb94a6741819 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 5794ae515afd7..a173d559d222e 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 75a7a05e00db5..cee26cb32e97f 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -188,7 +188,7 @@ "label": "cloud", "description": [], "signature": [ - "{ readonly indexManagement: string; }" + "{ readonly beatsAndLogstashConfiguration: string; readonly indexManagement: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 42369cbd064a9..2e4c7fa347e88 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 077724c079f35..71b4cbd81c709 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index d8e78c4e7ed19..061b47c13e746 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index d2a41664d9963..0b4cc22b82563 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 7e19bf2b752c4..121c9653054c7 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index b4bbfc4268b87..6670939a4580d 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 4a6e9ec026c93..1b755b46a7084 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 6d8467150869d..6fe90016c8f9c 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 45b31d05190d8..11e18c47f2283 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index ca836e5a68fa5..c82d160863404 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 6984a273d3af6..dce10c7d9cc48 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 1122fe1ce3477..75f5dafad9657 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index b4eb28b97ce35..37dd013e2e9bf 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 778bd22ea1f3c..842da802e8d72 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index 40ff5c5b4c600..a4ed1afd55a0c 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index be3eb2206755e..016214c2442c6 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 2c858b2235f65..41f15802921c4 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index 74e00a5d67edd..2dcf45be5d961 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 72e5fac960de5..b8300f07fa8c9 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 9ee48740b5764..d9c86fb79b923 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index b99f419b54347..120ce5dd3c1d7 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index fcc83b8376c8f..d7c42f6b1177e 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index bb4533189accb..407a581068629 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index 69eccfe98f835..fb41a7be4951d 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index daba723741ac9..e6ff63d52d665 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 1796d99213e7e..1edc5f33884b5 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index c835b7449e29b..bd10bc2d9a904 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 6e2bbcdaa7d26..05f48bf23ee11 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 802384e5daf27..77bcf96808129 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 531e5dcc8bfed..b4208ba4ec2b2 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 7c76d20f12ede..e19cdc24528aa 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 9b9086ffd45dd..13abdae24ddb7 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 0cdf73959282d..15bd5a0f10a4d 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 43674dae39aaf..f16d6856cb0af 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index b9127f2abfb62..1c74c25d20736 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 807f4284217f9..f4e6f4f42405d 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 0fd78170433ef..48666ddfc9cec 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 7a81141e28b12..848b68a06bd6a 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index e1ab9a890e3c0..2f019fa045986 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 222e4c77b7a3c..05922365f5a79 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 8c96c2f07fdd5..1d0d26cfc40b3 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index ffaf394dbc42c..8f9c991cdecda 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 680ad1778fa9c..154560575a7f9 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 19156a0ee662d..c3bec508ab7f1 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 4c5a8a0670f4e..4a87a3188f490 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 33600c2a5e444..8ff5aaf0d9e99 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index 775e1bd10c9f1..44d2da6fd14cf 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index 607b8416872d8..62c9441a3eb77 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index 69d7cda180831..6eb634d838a92 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index cbbbef37dcd8b..b6c6f46f5bc92 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index 4ec1cde8b5c29..46f4605160b0f 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index 05abf0dbe9b38..f0612772a9130 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.devdocs.json b/api_docs/kbn_management_settings_ids.devdocs.json index ecc7289a5b4ca..af86fa04d57b9 100644 --- a/api_docs/kbn_management_settings_ids.devdocs.json +++ b/api_docs/kbn_management_settings_ids.devdocs.json @@ -877,21 +877,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/management-settings-ids", - "id": "def-common.LABS_DASHBOARD_CONTROLS_ID", - "type": "string", - "tags": [], - "label": "LABS_DASHBOARD_CONTROLS_ID", - "description": [], - "signature": [ - "\"labs:dashboard:dashboardControls\"" - ], - "path": "packages/kbn-management/settings/setting_ids/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/management-settings-ids", "id": "def-common.LABS_DASHBOARD_DEFER_BELOW_FOLD_ID", diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index c880732b72c95..e5a8c6eb448e5 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux @elastic/platform-deployment-management](https: | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 130 | 0 | 130 | 0 | +| 129 | 0 | 129 | 0 | ## Common diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 3b9e1a218084e..e6b1e32ab8793 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index 4bb988419f410..a353f360102ec 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index b01ebdbac230a..ddbcb477578db 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index 95d94828b5db2..e17fd9d2ad089 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 0a86e03917f78..8ce869fb39d5f 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 45ff4ac5d2a41..53b634d36c7f6 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index bfae56ba74719..d6d3ae73c69b2 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index 8c8e2328183c7..c8e93c3ddea8d 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index c30458b0acf95..4d3fb9af4a8a7 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index c0276887534e9..5dc7834d58d59 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index e794e82dfed8d..59ab90c0a6adf 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index f07e1df738435..e853c97f1a6b0 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index f62e4d8528a36..3492660885129 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index 2b875575da69e..b9b793835e67d 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index 27d414c534ce9..04d6919d1b23d 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index 03f6cbceb182a..abc4eaa0cdadd 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index e93465d920b96..66e526559a636 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 00eb220d3e6f5..bfe09917244e4 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index a377d6146b038..8f3f81cf3c505 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 679fac32be272..ffe74b9f673f4 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 8289bff3f047d..2b4aac21cee0c 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index db521752afb3e..3a75455a6491d 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index b2a9594414537..7250ccecabd5b 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 82d819de0bbbd..1e7856616884c 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 161312d725bc0..5d706d8a85b72 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index ff10f9ce450d3..e52a1837efdec 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 9c6b8a5cfbdbd..a853ed0a0b114 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 8ce19c2badec4..fcb1f56dee9f1 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index e72e78edc2bff..043732429d6a5 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index a83806f0da4d5..864093ed30dc5 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 3694965ad35ba..e1c511aee3913 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index 9e6872b77b9bd..04e01a1073742 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index adb1d9df859cb..b471610fb0237 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 16c7c62b140f6..b79ea3b08a41b 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 50f92353f385d..0de94fe6ca94c 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 34d61d993372e..de2a2a89c76a6 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 621b9bc05229e..f7cf3fba50192 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 27f9fdd17416e..964b3d6380eb6 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 9409382c8f169..3bf118f383598 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index 135e886dc7603..5b4174c8914fc 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index ef6b5637e8be1..3d9b4ef0ba95f 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 5d2455c7bba87..0d2ecb7e3b1aa 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 03245f883d8df..6576b0a7ddcff 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index 62c70b80a207b..8fb4687c239d1 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index 64506e753ceac..43ab49c010d42 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index e3bbe44bc18b6..5ace3de00d277 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index 7e3fa558c9c2b..985f642480e8f 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index ad9cd22db6fc8..b827aec1f27c0 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index c2c863ac898a3..91758cbb04735 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index 1c83c5a7e7495..339f64c0bbc50 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index ef76acadbceca..2d0ea0730a03e 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index bcdb0b1bf55a3..e1c5b7b8ebaba 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index f29edb9bf49ef..4ccc2b84d469b 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 556bbb8ca4c14..637a92a201230 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index f1c2c6a3cb803..294ebefe9e395 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index f743a6c7a333a..6a983ac74ac8a 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 4c5c244cbaef4..073e38d1eabf2 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 290cfe4a3d9d2..f842c0e6a27fd 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index 7d0b1f461c57a..80bd8ae63c66b 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.devdocs.json b/api_docs/kbn_search_connectors.devdocs.json index df053f0d16c45..8529e76a07783 100644 --- a/api_docs/kbn_search_connectors.devdocs.json +++ b/api_docs/kbn_search_connectors.devdocs.json @@ -11766,7 +11766,7 @@ "label": "ui_restrictions", "description": [], "signature": [ - "never[]" + "string[]" ], "path": "packages/kbn-search-connectors/types/native_connectors.ts", "deprecated": false, @@ -13408,7 +13408,7 @@ "label": "ui_restrictions", "description": [], "signature": [ - "never[]" + "string[]" ], "path": "packages/kbn-search-connectors/types/native_connectors.ts", "deprecated": false, diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 2d754ac3a65c3..839d9f822eab4 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index c23e1ec58533f..26a2c7dbb4a1b 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index c3a5bd57d8f47..8d4046d543b16 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 691f1ba65ec66..473fc3184c24c 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 588ca64dfff18..fb1df2770b543 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index adfc46027aa27..58971a53d23eb 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 76ee327c99b68..581d134caaed0 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index dbb193fe468de..820cf495c1182 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 76a6fe0f6f57f..da901ae0d6284 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 7d0fe4828ba41..29c179a0fecaa 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index d06c75f34eb45..248aad26136f0 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index df4bcaa7c6358..23c0ca4621bd8 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 5c7065c3f0652..3ebd33d10ff7f 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 17034c5f9cb7d..7d24f1cc3d852 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 9f7126511b9b6..7635c96aadd53 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index d65f324dc265a..803cb1949a31f 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 1812401ae1cb0..8401a1a09bee2 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 9dd34102854b4..a06b6aacb1d98 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 22a1f04ea6732..efa8395b4e0f9 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index f8e7c545d0509..92b3045410100 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index a6fde3d181fc0..db461ea8e0811 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 5b6422e02ce86..392d9a719ceb7 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index b91036b3309ef..0abc7d86c71e9 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index dceae2c956a1f..a3156b9c180c7 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index f0200a83cd4bc..9c99c4e6df525 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 5d3f4fe785c9c..04c4106c8bdb1 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 84b45e474c565..611378407b0e4 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index f38b714bd8ec2..3b478084bb9ca 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index bc06e82df30a7..3946e77a11ddb 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index a3dff22f7988b..5759a779a66b9 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 6e2eae9c6cadc..eaf113619c6f0 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 1717d3a540031..a624134e71bba 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 2bd4dd85d9954..a83cb445c1b59 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index b64eccc7c315f..e53a2454e120a 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index ecde095896f1f..a0a9a70e2d492 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index 30a31ed84ce1a..6c0bebea78feb 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 07e325309bfe9..89a30741d2683 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index a82f82657435a..757cad465eca8 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 44d38d6db1852..03ba2b95898aa 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index ead25f95a88c7..16c060829c1c3 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index 3ac3a5e2d7c0b..604d9ade9e422 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index f642bcc0f9c5e..8f8b48bdb0b4f 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index c8114f2050fac..103c3e199e84c 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index c42e9e64f4a35..b93c4501c6567 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 6581fdff9a168..e285518fbc89b 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index acf32839a2209..a7c692c70dd54 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index b071e2b160fb2..b0f7cdcb6c223 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index e039e5689a9a6..aec87195144fb 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index da0d259722169..3aab43ab75590 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 4d4ece316f2db..cdec34b509ca8 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 50622d414b213..dafcff0aa1e71 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 791ec1a5912f5..438f40ee4971e 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index 50243ceda1a69..cff5cc18e8622 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 7cafff329d8b4..bacbe59f29a7e 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 9c14e59603851..5a34147e5b2f3 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 312e486e73de7..2d733c6d4424c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index a4f249b88185b..31bfe6ab032ca 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index d41b2264c5e67..a928674de2ed9 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 77a323a3acae0..e681893812118 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.devdocs.json b/api_docs/kbn_shared_ux_page_no_data.devdocs.json index 9aa9a10503b95..cbd11a8a06a90 100644 --- a/api_docs/kbn_shared_ux_page_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_page_no_data.devdocs.json @@ -27,7 +27,7 @@ "label": "NoDataPage", "description": [], "signature": [ - "({ solution, logo, action, docsLink, pageTitle, className, }: ", + "({ solution, logo, action, docsLink, pageTitle, pageDescription, className, }: ", "NoDataPageProps", ") => JSX.Element" ], @@ -40,7 +40,7 @@ "id": "def-common.NoDataPage.$1", "type": "Object", "tags": [], - "label": "{\n solution,\n logo,\n action,\n docsLink,\n pageTitle,\n className,\n}", + "label": "{\n solution,\n logo,\n action,\n docsLink,\n pageTitle,\n pageDescription,\n className,\n}", "description": [], "signature": [ "NoDataPageProps" @@ -212,6 +212,22 @@ "path": "packages/shared-ux/page/no_data/types/index.d.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-page-no-data", + "id": "def-common.NoDataPageProps.pageDescription", + "type": "string", + "tags": [], + "label": "pageDescription", + "description": [ + "\nOptionally replace the auto-generated page description" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/page/no_data/types/index.d.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index a03749c4d291a..3196b3395a923 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 13 | 0 | 5 | 0 | +| 14 | 0 | 5 | 0 | ## Common diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index a1cb9bd43c550..163993837ea4e 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 66bafb7b36866..6ddfaeb70e28d 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 12c573676072c..0bcf0ba0d3d6f 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index acb6fed5da0b7..dd699c2a0d41d 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index ad7e3f154d525..0ddf8229b516b 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 1a31f4c29b68c..fcf82ae723919 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 8be35ce8e8d36..8115cd2a7b40d 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 6805131f657fc..4fe95f038ad9d 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 96f78ab04d97c..96ac0cea196ae 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 321893a4c6f4a..85308bd5904e2 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index e4d9b70139bef..3e1b629f53858 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 5de8387bb9180..130e614cef7d8 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.devdocs.json b/api_docs/kbn_slo_schema.devdocs.json index 84ef4fa26f48d..dd2e3dbef0a5a 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -482,7 +482,7 @@ "label": "CreateSLOInput", "description": [], "signature": [ - "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; } & { id?: string | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" + "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; } & { id?: string | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -497,7 +497,7 @@ "label": "CreateSLOParams", "description": [], "signature": [ - "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", + "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -631,7 +631,7 @@ "\nThe response type for /internal/observability/slo/_definitions\n" ], "signature": [ - "({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]" + "({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -661,7 +661,7 @@ "label": "FindSLOResponse", "description": [], "signature": [ - "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }" + "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -676,7 +676,7 @@ "label": "GetPreviewDataParams", "description": [], "signature": [ - "{ indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }" + "{ indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -751,7 +751,7 @@ "label": "GetSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -796,7 +796,7 @@ "label": "Indicator", "description": [], "signature": [ - "{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }" + "{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -811,7 +811,7 @@ "label": "IndicatorType", "description": [], "signature": [ - "\"sli.apm.transactionDuration\" | \"sli.apm.transactionErrorRate\" | \"sli.kql.custom\" | \"sli.metric.custom\" | \"sli.histogram.custom\"" + "\"sli.apm.transactionDuration\" | \"sli.apm.transactionErrorRate\" | \"sli.kql.custom\" | \"sli.metric.custom\" | \"sli.metric.timeslice\" | \"sli.histogram.custom\"" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -871,7 +871,7 @@ "label": "SLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -886,7 +886,67 @@ "label": "SLOWithSummaryResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.TimesclieMetricPercentileMetric", + "type": "Type", + "tags": [], + "label": "TimesclieMetricPercentileMetric", + "description": [], + "signature": [ + "{ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.TimesliceMetricBasicMetricWithField", + "type": "Type", + "tags": [], + "label": "TimesliceMetricBasicMetricWithField", + "description": [], + "signature": [ + "{ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.TimesliceMetricDocCountMetric", + "type": "Type", + "tags": [], + "label": "TimesliceMetricDocCountMetric", + "description": [], + "signature": [ + "{ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.TimesliceMetricIndicator", + "type": "Type", + "tags": [], + "label": "TimesliceMetricIndicator", + "description": [], + "signature": [ + "{ type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -916,7 +976,7 @@ "label": "UpdateSLOInput", "description": [], "signature": [ - "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; } | undefined; budgetingMethod?: \"occurrences\" | \"timeslices\" | undefined; objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }) | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" + "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; } | undefined; budgetingMethod?: \"occurrences\" | \"timeslices\" | undefined; objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }) | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -931,7 +991,7 @@ "label": "UpdateSLOParams", "description": [], "signature": [ - "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -986,7 +1046,7 @@ "label": "UpdateSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -1394,6 +1454,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -1990,6 +2120,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -2402,6 +2602,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -2782,25 +3052,29 @@ "TypeC", "<{ type: ", "LiteralC", - "<\"sli.histogram.custom\">; params: ", + "<\"sli.metric.timeslice\">; params: ", "IntersectionC", "<[", "TypeC", "<{ index: ", "StringC", - "; timestampField: ", - "StringC", - "; good: ", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", "UnionC", "<[", "IntersectionC", "<[", "TypeC", - "<{ field: ", + "<{ name: ", "StringC", "; aggregation: ", - "LiteralC", - "<\"value_count\">; }>, ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", "PartialC", "<{ filter: ", "StringC", @@ -2808,25 +3082,91 @@ "IntersectionC", "<[", "TypeC", - "<{ field: ", + "<{ name: ", "StringC", "; aggregation: ", "LiteralC", - "<\"range\">; from: ", - "NumberC", - "; to: ", - "NumberC", - "; }>, ", + "<\"doc_count\">; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>]>; total: ", - "UnionC", - "<[", + "; }>]>, ", "IntersectionC", "<[", "TypeC", - "<{ field: ", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"sli.histogram.custom\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; timestampField: ", + "StringC", + "; good: ", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ field: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"value_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ field: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"range\">; from: ", + "NumberC", + "; to: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>; total: ", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ field: ", "StringC", "; aggregation: ", "LiteralC", @@ -3206,6 +3546,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -3758,6 +4168,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -3872,6 +4352,8 @@ "LiteralC", "<\"sli.metric.custom\">, ", "LiteralC", + "<\"sli.metric.timeslice\">, ", + "LiteralC", "<\"sli.histogram.custom\">]>" ], "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", @@ -4413,6 +4895,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -4771,21 +5323,91 @@ "TypeC", "<{ type: ", "LiteralC", - "<\"sli.histogram.custom\">; params: ", + "<\"sli.metric.timeslice\">; params: ", "IntersectionC", "<[", "TypeC", "<{ index: ", "StringC", - "; timestampField: ", - "StringC", - "; good: ", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", "UnionC", "<[", "IntersectionC", "<[", "TypeC", - "<{ field: ", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"sli.histogram.custom\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; timestampField: ", + "StringC", + "; good: ", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ field: ", "StringC", "; aggregation: ", "LiteralC", @@ -5125,6 +5747,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -5511,6 +6203,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -5812,58 +6574,358 @@ }, { "parentPluginId": "@kbn/slo-schema", - "id": "def-common.timeslicesBudgetingMethodSchema", + "id": "def-common.timesliceMetricBasicMetricWithField", "type": "Object", "tags": [], - "label": "timeslicesBudgetingMethodSchema", + "label": "timesliceMetricBasicMetricWithField", "description": [], "signature": [ - "LiteralC", - "<\"timeslices\">" + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>" ], - "path": "x-pack/packages/kbn-slo-schema/src/schema/slo.ts", + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/slo-schema", - "id": "def-common.timeWindowSchema", + "id": "def-common.timesliceMetricComparatorMapping", "type": "Object", "tags": [], - "label": "timeWindowSchema", + "label": "timesliceMetricComparatorMapping", "description": [], - "signature": [ - "UnionC", - "<[", - "TypeC", - "<{ duration: ", - "Type", - "<", + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "@kbn/slo-schema", - "scope": "common", - "docId": "kibKbnSloSchemaPluginApi", - "section": "def-common.Duration", - "text": "Duration" + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricComparatorMapping.GT", + "type": "string", + "tags": [], + "label": "GT", + "description": [], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false }, - ", string, unknown>; type: ", - "LiteralC", - "<\"rolling\">; }>, ", - "TypeC", - "<{ duration: ", - "Type", - "<", { - "pluginId": "@kbn/slo-schema", - "scope": "common", - "docId": "kibKbnSloSchemaPluginApi", - "section": "def-common.Duration", - "text": "Duration" + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricComparatorMapping.GTE", + "type": "string", + "tags": [], + "label": "GTE", + "description": [], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false }, - ", string, unknown>; type: ", - "LiteralC", - "<\"calendarAligned\">; }>]>" + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricComparatorMapping.LT", + "type": "string", + "tags": [], + "label": "LT", + "description": [], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricComparatorMapping.LTE", + "type": "string", + "tags": [], + "label": "LTE", + "description": [], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricDocCountMetric", + "type": "Object", + "tags": [], + "label": "timesliceMetricDocCountMetric", + "description": [], + "signature": [ + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricIndicatorSchema", + "type": "Object", + "tags": [], + "label": "timesliceMetricIndicatorSchema", + "description": [], + "signature": [ + "TypeC", + "<{ type: ", + "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricIndicatorTypeSchema", + "type": "Object", + "tags": [], + "label": "timesliceMetricIndicatorTypeSchema", + "description": [], + "signature": [ + "LiteralC", + "<\"sli.metric.timeslice\">" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricMetricDef", + "type": "Object", + "tags": [], + "label": "timesliceMetricMetricDef", + "description": [], + "signature": [ + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timesliceMetricPercentileMetric", + "type": "Object", + "tags": [], + "label": "timesliceMetricPercentileMetric", + "description": [], + "signature": [ + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timeslicesBudgetingMethodSchema", + "type": "Object", + "tags": [], + "label": "timeslicesBudgetingMethodSchema", + "description": [], + "signature": [ + "LiteralC", + "<\"timeslices\">" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.timeWindowSchema", + "type": "Object", + "tags": [], + "label": "timeWindowSchema", + "description": [], + "signature": [ + "UnionC", + "<[", + "TypeC", + "<{ duration: ", + "Type", + "<", + { + "pluginId": "@kbn/slo-schema", + "scope": "common", + "docId": "kibKbnSloSchemaPluginApi", + "section": "def-common.Duration", + "text": "Duration" + }, + ", string, unknown>; type: ", + "LiteralC", + "<\"rolling\">; }>, ", + "TypeC", + "<{ duration: ", + "Type", + "<", + { + "pluginId": "@kbn/slo-schema", + "scope": "common", + "docId": "kibKbnSloSchemaPluginApi", + "section": "def-common.Duration", + "text": "Duration" + }, + ", string, unknown>; type: ", + "LiteralC", + "<\"calendarAligned\">; }>]>" ], "path": "x-pack/packages/kbn-slo-schema/src/schema/time_window.ts", "deprecated": false, @@ -6069,6 +7131,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -6413,6 +7545,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index e33b5fc5d55e9..0b94a1c7f0c52 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 114 | 0 | 111 | 0 | +| 129 | 0 | 126 | 0 | ## Common diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 601f701aba6de..d3fc2ece66b32 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 360f59651ac4e..bccf9dc1abe80 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index f80241b8a7b1b..992c81515ae93 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 60534bcecd03c..264d5c829f1d3 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_subscription_tracking.mdx b/api_docs/kbn_subscription_tracking.mdx index b22526243451f..4867e5b701a0a 100644 --- a/api_docs/kbn_subscription_tracking.mdx +++ b/api_docs/kbn_subscription_tracking.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-subscription-tracking title: "@kbn/subscription-tracking" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/subscription-tracking plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/subscription-tracking'] --- import kbnSubscriptionTrackingObj from './kbn_subscription_tracking.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index e72a754c57868..4529c425422d5 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 763603520c30a..6e7f593ae72e8 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 6a4bb94b31940..6e6a45e378cc4 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index b305e38ed3e9f..17f6b1cf9dd8a 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index 998985e2fe1e1..e9a094a1089db 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index e498dc4f106da..4e271207f51fb 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 982d3770d9518..2978d71f6f721 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 3deb9c6c6f737..4992931859832 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 1a5640c3cf5a2..95a2a02f71700 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 84387ccfad6ca..0589e914d5e0d 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index d2ae1c8cbd95e..84ef16967e4f0 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index d339227876a37..aca3f99a69d6c 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 8d9953d71987b..c194966799af7 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index 0f353e7b39a1f..046f2b9b53f0a 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index 7ee704edbc2a9..59f1bcf55e22a 100644 --- a/api_docs/kbn_url_state.mdx +++ b/api_docs/kbn_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-url-state title: "@kbn/url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/url-state plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/url-state'] --- import kbnUrlStateObj from './kbn_url_state.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 113aa81acd685..3b9f8a91d0cdd 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 6f321aa90f9cb..de54339151c02 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index d394b4e205e93..15497829701cd 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 078bc8f871343..bf8b0324e8777 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 6a758510c9549..aa2a7cf43f270 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index a5797f2ebbb34..4142559044ae1 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index 39f61d949e2a3..6df7e6d4520c2 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 0b4c4b9c7abc4..b4d528ec71f66 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 990bb033de9fa..f1a1342e4dec1 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 9f9f1f16ad307..356e6885ddb0e 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index d4c39c3ce8559..00d4e387d403b 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 5e212795a610e..9ec5362ee8f21 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index e6e13076d33d9..b710772b962fa 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index f9c93353a4bda..8bc9e9b56019b 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 85a8d00409ba3..6db00695ad97b 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index bcb050aa867b7..d52f843a6a51e 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index d77ce47daed1d..aa096e4204efe 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 5ecf86ee60aa6..057bc70d4f68c 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/log_explorer.mdx b/api_docs/log_explorer.mdx index 9d6d41599a2cc..ead2caa9109eb 100644 --- a/api_docs/log_explorer.mdx +++ b/api_docs/log_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logExplorer title: "logExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logExplorer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logExplorer'] --- import logExplorerObj from './log_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index 36bdcdaa4fbce..b97b80a200796 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 1822e5225afbd..ed18839c0f9a5 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 2f4bccc5d6051..1c82b46486312 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.devdocs.json b/api_docs/maps_ems.devdocs.json index b1ca9cb4581c9..697a5dd4c1c2f 100644 --- a/api_docs/maps_ems.devdocs.json +++ b/api_docs/maps_ems.devdocs.json @@ -1021,6 +1021,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "mapsEms", + "id": "def-common.DEFAULT_EMS_REST_VERSION", + "type": "string", + "tags": [], + "label": "DEFAULT_EMS_REST_VERSION", + "description": [], + "signature": [ + "\"2023-10-31\"" + ], + "path": "src/plugins/maps_ems/common/ems_defaults.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "mapsEms", "id": "def-common.DEFAULT_EMS_ROADMAP_DESATURATED_ID", diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 1b36ada3381a7..c5244b8945b65 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 67 | 0 | 67 | 0 | +| 68 | 0 | 68 | 0 | ## Client diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index 7842beec9380a..f5aba53735bad 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index b7909b8724407..3556b058709f8 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 84de0f543d4c0..9a32b39fcbf45 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 36021747f1c4f..599351943ffb5 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index a29a276eaedf0..2ea44d9a80926 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 50862e5d7898d..7900fdb4062ad 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index 42f3c067fdb43..817cc99999a6a 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 2375ef39afb21..e3dd51d87a4b8 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index a4d690ff05845..6dcd31dc1319b 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -8310,6 +8310,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -8390,7 +8460,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", + " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -8646,6 +8716,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -8814,7 +8954,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -8854,7 +8994,7 @@ "section": "def-common.Duration", "text": "Duration" }, - " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", + " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -8888,7 +9028,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", + " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -8930,7 +9070,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", + " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -8952,7 +9092,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", + " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -9256,6 +9396,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -9428,7 +9638,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", + " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -9720,6 +9930,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -9800,7 +10080,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", + " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -10056,6 +10336,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -10224,7 +10574,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -10264,7 +10614,7 @@ "section": "def-common.Duration", "text": "Duration" }, - " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", + " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -10298,7 +10648,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", + " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -10340,7 +10690,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", + " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -10362,7 +10712,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", + " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -10666,6 +11016,76 @@ "TypeC", "<{ type: ", "LiteralC", + "<\"sli.metric.timeslice\">; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ index: ", + "StringC", + "; metric: ", + "TypeC", + "<{ metrics: ", + "ArrayC", + "<", + "UnionC", + "<[", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "KeyofC", + "<{ avg: boolean; max: boolean; min: boolean; sum: boolean; cardinality: boolean; last_value: boolean; std_deviation: boolean; }>; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"percentile\">; field: ", + "StringC", + "; percentile: ", + "NumberC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", + "StringC", + "; threshold: ", + "NumberC", + "; comparator: ", + "KeyofC", + "<{ GT: string; GTE: string; LT: string; LTE: string; }>; }>; timestampField: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", "<\"sli.histogram.custom\">; params: ", "IntersectionC", "<[", @@ -10838,7 +11258,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", + " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 1f2dd6d435511..a8c34f4991531 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 2961a6a81c6ce..416537167873a 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_log_explorer.mdx b/api_docs/observability_log_explorer.mdx index c54d8e6f367c7..ab026979c2c7c 100644 --- a/api_docs/observability_log_explorer.mdx +++ b/api_docs/observability_log_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogExplorer title: "observabilityLogExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogExplorer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogExplorer'] --- import observabilityLogExplorerObj from './observability_log_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 62bb7c343f76b..7011f6731c70e 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 1f3ea1fdcdef8..7f19843924287 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index d2e98c6f29bc8..08b6c3de2f400 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index afffae5c91e52..a6ef5e8abf069 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index ff30adf2672fc..f4d2592ee81bb 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 75848 | 223 | 64648 | 1583 | +| 75857 | 223 | 64656 | 1584 | ## Plugin Directory @@ -95,7 +95,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 59 | 0 | 59 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 239 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 0 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1208 | 3 | 1090 | 43 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1209 | 3 | 1091 | 44 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -128,7 +128,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 47 | 0 | 47 | 7 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 259 | 0 | 258 | 28 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 68 | 0 | 68 | 0 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Exposes utilities for accessing metrics data | 16 | 0 | 16 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 150 | 3 | 64 | 33 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 15 | 3 | 13 | 1 | @@ -144,7 +144,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 291 | 1 | 288 | 15 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 24 | 0 | 24 | 7 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 227 | 2 | 172 | 11 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 219 | 2 | 164 | 11 | | | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 16 | 1 | 16 | 0 | | | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 21 | 0 | 21 | 6 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 23 | 0 | 23 | 0 | @@ -485,7 +485,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 23 | 0 | 7 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 2 | 3 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 45 | 0 | 0 | 0 | -| | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 130 | 0 | 130 | 0 | +| | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 129 | 0 | 129 | 0 | | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 20 | 0 | 11 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 81 | 0 | 3 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 54 | 0 | 6 | 0 | @@ -604,7 +604,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 25 | 0 | 24 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 11 | 0 | 6 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 43 | 5 | 43 | 2 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 13 | 0 | 5 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 14 | 0 | 5 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 11 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 24 | 0 | 24 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 27 | 0 | 26 | 0 | @@ -617,7 +617,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 15 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 3 | 0 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 114 | 0 | 111 | 0 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 129 | 0 | 126 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 20 | 0 | 12 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 102 | 2 | 65 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 4 | 0 | 2 | 0 | diff --git a/api_docs/presentation_util.devdocs.json b/api_docs/presentation_util.devdocs.json index 91660f313ddef..6a4ce95f96a32 100644 --- a/api_docs/presentation_util.devdocs.json +++ b/api_docs/presentation_util.devdocs.json @@ -1543,7 +1543,7 @@ "label": "isProjectEnabled", "description": [], "signature": [ - "(id: \"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\") => boolean" + "(id: \"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\") => boolean" ], "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, @@ -1557,7 +1557,7 @@ "label": "id", "description": [], "signature": [ - "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" + "\"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" ], "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, @@ -1575,7 +1575,7 @@ "label": "getProjectIDs", "description": [], "signature": [ - "() => readonly [\"labs:dashboard:deferBelowFold\", \"labs:dashboard:dashboardControls\", \"labs:canvas:byValueEmbeddable\", \"labs:dashboard:linksPanel\"]" + "() => readonly [\"labs:dashboard:deferBelowFold\", \"labs:canvas:byValueEmbeddable\", \"labs:dashboard:linksPanel\"]" ], "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, @@ -1591,7 +1591,7 @@ "label": "getProject", "description": [], "signature": [ - "(id: \"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\") => ", + "(id: \"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\") => ", { "pluginId": "presentationUtil", "scope": "common", @@ -1612,7 +1612,7 @@ "label": "id", "description": [], "signature": [ - "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" + "\"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" ], "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, @@ -1630,7 +1630,7 @@ "label": "getProjects", "description": [], "signature": [ - "(solutions?: (\"canvas\" | \"dashboard\" | \"presentation\")[] | undefined) => Record<\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\", ", + "(solutions?: (\"canvas\" | \"dashboard\" | \"presentation\")[] | undefined) => Record<\"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\", ", { "pluginId": "presentationUtil", "scope": "common", @@ -1670,7 +1670,7 @@ "label": "setProjectStatus", "description": [], "signature": [ - "(id: \"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\", env: \"kibana\" | \"browser\" | \"session\", status: boolean) => void" + "(id: \"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\", env: \"kibana\" | \"browser\" | \"session\", status: boolean) => void" ], "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, @@ -1684,7 +1684,7 @@ "label": "id", "description": [], "signature": [ - "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" + "\"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" ], "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, @@ -3079,7 +3079,7 @@ "label": "getProjectIDs", "description": [], "signature": [ - "() => readonly [\"labs:dashboard:deferBelowFold\", \"labs:dashboard:dashboardControls\", \"labs:canvas:byValueEmbeddable\", \"labs:dashboard:linksPanel\"]" + "() => readonly [\"labs:dashboard:deferBelowFold\", \"labs:canvas:byValueEmbeddable\", \"labs:dashboard:linksPanel\"]" ], "path": "src/plugins/presentation_util/common/labs.ts", "deprecated": false, @@ -3381,7 +3381,7 @@ "label": "id", "description": [], "signature": [ - "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" + "\"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" ], "path": "src/plugins/presentation_util/common/labs.ts", "deprecated": false, @@ -3595,7 +3595,7 @@ "label": "ProjectID", "description": [], "signature": [ - "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" + "\"labs:dashboard:deferBelowFold\" | \"labs:canvas:byValueEmbeddable\" | \"labs:dashboard:linksPanel\"" ], "path": "src/plugins/presentation_util/common/labs.ts", "deprecated": false, @@ -3688,7 +3688,7 @@ "label": "projectIDs", "description": [], "signature": [ - "readonly [\"labs:dashboard:deferBelowFold\", \"labs:dashboard:dashboardControls\", \"labs:canvas:byValueEmbeddable\", \"labs:dashboard:linksPanel\"]" + "readonly [\"labs:dashboard:deferBelowFold\", \"labs:canvas:byValueEmbeddable\", \"labs:dashboard:linksPanel\"]" ], "path": "src/plugins/presentation_util/common/labs.ts", "deprecated": false, @@ -3813,111 +3813,6 @@ } ] }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS", - "type": "Object", - "tags": [], - "label": "[DASHBOARD_CONTROLS]", - "description": [], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "\"labs:dashboard:dashboardControls\"" - ], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.isActive", - "type": "boolean", - "tags": [], - "label": "isActive", - "description": [], - "signature": [ - "true" - ], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.isDisplayed", - "type": "boolean", - "tags": [], - "label": "isDisplayed", - "description": [], - "signature": [ - "true" - ], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.environments", - "type": "Array", - "tags": [], - "label": "environments", - "description": [], - "signature": [ - "(\"kibana\" | \"browser\" | \"session\")[]" - ], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.name", - "type": "string", - "tags": [], - "label": "name", - "description": [], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "presentationUtil", - "id": "def-common.projects.DASHBOARD_CONTROLS.solutions", - "type": "Array", - "tags": [], - "label": "solutions", - "description": [], - "signature": [ - "\"dashboard\"[]" - ], - "path": "src/plugins/presentation_util/common/labs.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, { "parentPluginId": "presentationUtil", "id": "def-common.projects.DASHBOARD_LINKS_PANEL", diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index cc37678a7e8c4..36bea637e3894 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 227 | 2 | 172 | 11 | +| 219 | 2 | 164 | 11 | ## Client diff --git a/api_docs/profiling.devdocs.json b/api_docs/profiling.devdocs.json index f3e6f3535e41b..6889fdd47e14e 100644 --- a/api_docs/profiling.devdocs.json +++ b/api_docs/profiling.devdocs.json @@ -38,7 +38,7 @@ "label": "PackageInputType", "description": [], "signature": [ - "{ readonly host?: string | undefined; readonly tls_enabled?: boolean | undefined; readonly tls_supported_protocols?: string[] | undefined; readonly tls_certificate_path?: string | undefined; readonly tls_key_path?: string | undefined; }" + "{ readonly telemetry?: boolean | undefined; readonly host?: string | undefined; readonly tls_enabled?: boolean | undefined; readonly tls_supported_protocols?: string[] | undefined; readonly tls_certificate_path?: string | undefined; readonly tls_key_path?: string | undefined; }" ], "path": "x-pack/plugins/profiling/server/index.ts", "deprecated": false, @@ -53,7 +53,7 @@ "label": "ProfilingConfig", "description": [], "signature": [ - "{ readonly elasticsearch?: Readonly<{} & { username: string; hosts: string; password: string; }> | undefined; readonly symbolizer?: Readonly<{ host?: string | undefined; tls_enabled?: boolean | undefined; tls_supported_protocols?: string[] | undefined; tls_certificate_path?: string | undefined; tls_key_path?: string | undefined; } & {}> | undefined; readonly collector?: Readonly<{ host?: string | undefined; tls_enabled?: boolean | undefined; tls_supported_protocols?: string[] | undefined; tls_certificate_path?: string | undefined; tls_key_path?: string | undefined; } & {}> | undefined; readonly enabled: boolean; }" + "{ readonly elasticsearch?: Readonly<{} & { username: string; hosts: string; password: string; }> | undefined; readonly symbolizer?: Readonly<{ telemetry?: boolean | undefined; host?: string | undefined; tls_enabled?: boolean | undefined; tls_supported_protocols?: string[] | undefined; tls_certificate_path?: string | undefined; tls_key_path?: string | undefined; } & {}> | undefined; readonly collector?: Readonly<{ telemetry?: boolean | undefined; host?: string | undefined; tls_enabled?: boolean | undefined; tls_supported_protocols?: string[] | undefined; tls_certificate_path?: string | undefined; tls_key_path?: string | undefined; } & {}> | undefined; readonly enabled: boolean; }" ], "path": "x-pack/plugins/profiling/server/index.ts", "deprecated": false, diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index afd424fe7a360..dd5f7f756c786 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index 5a747bb6dbd24..9914e54e13825 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 5dda7460e7b38..8e5ee24aa5497 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index d41d3a8d59c9c..2304babb907ab 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index b7bcb9ec392bd..0b48aca31d420 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index c18803a2717b5..4bf6ec08ffddc 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index c1277008f8005..896ce6fa44bb9 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 80d8aaedfb67c..439ea2fff556a 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index da82bb6d1b2f7..5f499d7bb62da 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 7191dcb6890cc..62e05fbc6de9d 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 36fcc83f23076..df4caeb535b3a 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 31ee8d4fe7f0a..9941d053c1973 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 7a89810328e82..08855b04fb4d5 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 500a8534117a1..7777811d6bd48 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 683f642a63c8c..a9000224992a6 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index e0914d3a0f545..c6e7131ab9b44 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index fa6840750f7ac..e2ae0f6b77616 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 65204810bf06b..fb12c4740c65a 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index f18f68ddcd89b..a94f1a91a3524 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 9e8e39c4eff2f..03d3ecb8e21e9 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index 4ba9d4516947e..6d566a7dd18eb 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index 1c6243dd3e5e4..9ce17a4384fb8 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 2ec3157b94ac0..11dd3ed299b86 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index a31883bcb1840..128e6c80f863a 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 32c52eaa951b1..4a0d1e447511d 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 02ff50607c619..0b88498825239 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 6ce70ad9b2266..b3f29202870c1 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 33ef37c292981..e4425b153a248 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index fb4f7a55f8546..6c89d01595531 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index be86cc25eaee9..b450df0b84cf1 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 2f08803e9e8b2..54fa854541196 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 47b3e7b690e4d..3bbf43d16218c 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 052f6706031c6..9030dc06710ee 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index 1cceb7aff2261..0564eea2132b2 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 6f2fbdf049fa1..b58ee3df6e625 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index b6402e947711f..9314ca84e996d 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 5047c0facd21e..eafa2122bebbd 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index a77aafebb5afd..5a4bec974db92 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 158859e2154f3..4f5485b484ae5 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 3a22305dcc133..fb201cb1ffa35 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index 7279ef7969fb7..eecfa3715e4b9 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 4f2ff48629358..1d8886c83fdfe 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 716bf96728f17..5bc0db815e546 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 20aa367104dcf..bc152247c68ee 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 7ad6d527b374b..e29055b1b9950 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 4ba133a68689c..5f0570c3fa0d5 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index e0f179e68ff10..2004fffdf9203 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 490dffba1e0c2..961badd1a3aa2 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index eedc2c019d23e..ebc68f992532d 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 15ba59a5a1275..6fd5f786330d0 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index ac23e0ff4e61e..4fc7751981ae9 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 83f111c2a1190..230f5545c332a 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 4c01c8841810a..9477acc939607 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 9a6a5f5a514a8..fdcba6bcad8cb 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index e0a2872da856d..2fc9b97219976 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 377733d865630..304bc7edc65b5 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 926628184f4ef..2317ed06e02a4 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index fb9e61c42d161..b4d84cdd1c6a9 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index e40e1b2ef4a6c..12ab849abf499 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -15214,7 +15214,7 @@ "label": "Operation", "description": [], "signature": [ - "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"range\" | \"date_histogram\" | \"percentile\" | \"average\" | \"terms\" | \"cumulative_sum\" | \"moving_average\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"normalize_by_unit\"" + "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"last_value\" | \"percentile\" | \"range\" | \"date_histogram\" | \"average\" | \"terms\" | \"cumulative_sum\" | \"moving_average\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"normalize_by_unit\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", "deprecated": false, @@ -15244,7 +15244,7 @@ "label": "OperationWithSourceField", "description": [], "signature": [ - "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"range\" | \"date_histogram\" | \"percentile\" | \"average\" | \"terms\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\"" + "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"last_value\" | \"percentile\" | \"range\" | \"date_histogram\" | \"average\" | \"terms\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", "deprecated": false, diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 8f0df503aa367..0df559e04aa93 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-10-17 +date: 2023-10-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 1d2d6e4cc6ff9a1dcc523906995a1797dbfd5478 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 18 Oct 2023 08:33:53 +0200 Subject: [PATCH 18/49] [Synthetics] Fix and refactor table row click actions for test runs table (#166915) --- .../common/components/view_document.tsx | 14 +++++-- .../journey_screenshot_preview.tsx | 4 +- .../common/screenshot/empty_thumbnail.tsx | 10 +++++ .../screenshot/journey_screenshot_dialog.tsx | 5 +++ .../monitor_summary/test_runs_table.tsx | 37 ++++++------------- .../simple/ping_list/columns/expand_row.tsx | 17 ++++++--- 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx index 591c91a902146..8f03ab6190af5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/view_document.tsx @@ -6,7 +6,7 @@ */ import { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useState, MouseEvent } from 'react'; import { useUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; @@ -56,12 +56,20 @@ export const ViewDocument = ({ ping }: { ping: Ping }) => { data-test-subj="syntheticsViewDocumentButton" iconType="inspect" title={INSPECT_DOCUMENT} - onClick={() => { + onClick={(evt: MouseEvent) => { + evt.stopPropagation(); setIsFlyoutVisible(true); }} /> {isFlyoutVisible && ( - setIsFlyoutVisible(false)} ownFocus={true}> + setIsFlyoutVisible(false)} + ownFocus={true} + onClick={(evt: MouseEvent) => { + // needed to prevent propagation to the table row click + evt.stopPropagation(); + }} + >

diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_screenshot_preview.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_screenshot_preview.tsx index 5eac5686e9923..f16586141971d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_screenshot_preview.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_screenshot_preview.tsx @@ -61,7 +61,9 @@ export const JourneyScreenshotPreview: React.FC = ({ ); const onImgClick = useCallback( - (_evt: MouseEvent) => { + (evt: MouseEvent) => { + // needed to prevent propagation to the table row click + evt.stopPropagation(); setIsImageEverClicked(true); setIsImageDialogOpen(true); setIsImagePopoverOpen(false); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx index 7309d54aa08da..45e1e780d1e31 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx @@ -49,6 +49,7 @@ export const EmptyThumbnail = ({ const noDataMessage = unavailableMessage ?? SCREENSHOT_NOT_AVAILABLE; return ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
{ + // We don't want the placeholder to be clickable + e.stopPropagation(); + e.preventDefault(); + }} + onKeyDown={(e) => { + // We don't want the placeholder to be clickable + e.stopPropagation(); + }} > {isLoading && animateLoading ? ( | MouseEvent) => { + // for table row click to work evt?.stopPropagation?.(); onClose(); }} @@ -127,6 +128,10 @@ export const JourneyScreenshotDialog = ({ animateLoading={false} hasBorder={false} size={'full'} + onClick={(evt) => { + // for table row click to work + evt.stopPropagation(); + }} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx index 5fe46b6c52697..de83a03ac4928 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx @@ -213,7 +213,7 @@ export const TestRunsTable = ({ name: MESSAGE_LABEL, textOnly: true, css: css` - max-width: 600px; + max-width: 500px; `, render: (errorMessage: string) => ( {errorMessage?.length > 0 ? errorMessage : '-'} @@ -269,31 +269,16 @@ export const TestRunsTable = ({ return { 'data-test-subj': `row-${item.monitor.check_group}`, onClick: (evt: MouseEvent) => { - const targetElem = evt.target as HTMLElement; - const isTableRow = - targetElem.parentElement?.classList.contains('euiTableCellContent') || - targetElem.parentElement?.classList.contains('euiTableCellContent__text') || - targetElem?.classList.contains('euiTableCellContent') || - targetElem?.classList.contains('euiBadge__text'); - // we dont want to capture image click event - if ( - isTableRow && - targetElem.tagName !== 'IMG' && - targetElem.tagName !== 'path' && - targetElem.tagName !== 'BUTTON' && - !targetElem.parentElement?.classList.contains('euiLink') - ) { - if (item.monitor.type !== MONITOR_TYPES.BROWSER) { - toggleDetails(item, expandedRows, setExpandedRows); - } else { - history.push( - getTestRunDetailRelativeLink({ - monitorId, - checkGroup: item.monitor.check_group, - locationId: selectedLocation?.id, - }) - ); - } + if (item.monitor.type !== MONITOR_TYPES.BROWSER) { + toggleDetails(item, expandedRows, setExpandedRows); + } else { + history.push( + getTestRunDetailRelativeLink({ + monitorId, + checkGroup: item.monitor.check_group, + locationId: selectedLocation?.id, + }) + ); } }, }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/simple/ping_list/columns/expand_row.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/simple/ping_list/columns/expand_row.tsx index 9ef5ceacc6183..13cef1917b1cc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/simple/ping_list/columns/expand_row.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/simple/ping_list/columns/expand_row.tsx @@ -5,16 +5,17 @@ * 2.0. */ -import React from 'react'; +import React, { Dispatch, SetStateAction, MouseEvent } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonIcon } from '@elastic/eui'; import { Ping } from '../../../../../../../../common/runtime_types'; import { PingListExpandedRowComponent } from '../expanded_row'; +type PingExpandedRowMap = Record; export const toggleDetails = ( ping: Ping, - expandedRows: Record, - setExpandedRows: (update: Record) => any + expandedRows: PingExpandedRowMap, + setExpandedRows: Dispatch> ) => { // prevent expanding on row click if not expandable if (!rowShouldExpand(ping)) { @@ -48,14 +49,18 @@ export function rowShouldExpand(item: Ping) { interface Props { item: Ping; - expandedRows: Record; - setExpandedRows: (val: Record) => void; + expandedRows: PingExpandedRowMap; + setExpandedRows: Dispatch>; } export const ExpandRowColumn = ({ item, expandedRows, setExpandedRows }: Props) => { return ( toggleDetails(item, expandedRows, setExpandedRows)} + onClick={(evt: MouseEvent) => { + // for table row click + evt.stopPropagation(); + toggleDetails(item, expandedRows, setExpandedRows); + }} isDisabled={!rowShouldExpand(item)} aria-label={ expandedRows[item.docId] From 414b1daa853424b2cd88dd70ec3910a15400e687 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 18 Oct 2023 08:43:13 +0200 Subject: [PATCH 19/49] [Discover] Unskip shared links test (#168923) Improves and unskips Discover's shared links tests, and makes them a few seconds faster on top Flaky test runner (100): https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3515 --- .../apps/discover/group1/_shared_links.ts | 36 ++++++------------- test/functional/services/toasts.ts | 6 +++- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/test/functional/apps/discover/group1/_shared_links.ts b/test/functional/apps/discover/group1/_shared_links.ts index e8f79ea1b427a..c6269cabfda98 100644 --- a/test/functional/apps/discover/group1/_shared_links.ts +++ b/test/functional/apps/discover/group1/_shared_links.ts @@ -33,11 +33,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { baseUrl = baseUrl.replace(':80', '').replace(':443', ''); log.debug('New baseUrl = ' + baseUrl); - // delete .kibana index and update configDoc - await kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', - }); - log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); @@ -45,17 +40,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ 'state:storeInSessionStorage': storeStateInSessionStorage, + defaultIndex: 'logstash-*', }); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); - log.debug('discover'); await PageObjects.common.navigateToApp('discover'); - - await PageObjects.timePicker.setDefaultAbsoluteRange(); - - // After hiding the time picker, we need to wait for - // the refresh button to hide before clicking the share button - await PageObjects.common.sleep(1000); - await PageObjects.share.clickShareTopNavButton(); return async () => { @@ -144,8 +133,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/167405 - describe.skip('shared links with state in sessionStorage', async () => { + describe('shared links with state in sessionStorage', async () => { let teardown: () => Promise; before(async function () { teardown = await setup({ storeStateInSessionStorage: true }); @@ -168,31 +156,29 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.clearSessionStorage(); await browser.get(actualUrl, false); - await retry.waitFor('shortUrl resolves and opens', async () => { + await retry.try(async () => { const resolvedUrl = await browser.getCurrentUrl(); expect(resolvedUrl).to.match(/discover/); const resolvedTime = await PageObjects.timePicker.getTimeConfig(); expect(resolvedTime.start).to.equal(actualTime.start); expect(resolvedTime.end).to.equal(actualTime.end); - await toasts.dismissAllToasts(); - return true; }); + await toasts.dismissAllToasts(); }); it("sharing hashed url shouldn't crash the app", async () => { const currentUrl = await browser.getCurrentUrl(); - await browser.clearSessionStorage(); - await browser.get(currentUrl, false); - await retry.waitFor('discover to open', async () => { + await retry.try(async () => { + await browser.clearSessionStorage(); + await browser.get(currentUrl, false); const resolvedUrl = await browser.getCurrentUrl(); expect(resolvedUrl).to.match(/discover/); - const { message } = await toasts.getErrorToast(); - expect(message).to.contain( + const { title } = await toasts.getErrorToast(1, true); + expect(title).to.contain( 'Unable to completely restore the URL, be sure to use the share functionality.' ); - await toasts.dismissAllToasts(); - return true; }); + await toasts.dismissAllToasts(); }); }); }); diff --git a/test/functional/services/toasts.ts b/test/functional/services/toasts.ts index ea105eb82fa93..a4d3b2dd3e457 100644 --- a/test/functional/services/toasts.ts +++ b/test/functional/services/toasts.ts @@ -19,12 +19,16 @@ export class ToastsService extends FtrService { * an additional button, that should not be part of the message. * * @param index The index of the toast (1-based, NOT 0-based!) of the toast. Use first by default. + * @param titleOnly If this is true, only the title of the error message is returned. There are error messages that only contain a title, no message. * @returns The title and message of the specified error toast.https://github.com/elastic/kibana/issues/17087 */ - public async getErrorToast(index: number = 1) { + public async getErrorToast(index: number = 1, titleOnly: boolean = false) { const toast = await this.getToastElement(index); const titleElement = await this.testSubjects.findDescendant('euiToastHeader', toast); const title: string = await titleElement.getVisibleText(); + if (titleOnly) { + return { title }; + } const messageElement = await this.testSubjects.findDescendant('errorToastMessage', toast); const message: string = await messageElement.getVisibleText(); return { title, message }; From 761fa5173c22dcca20ee4a6e499d85237f8773bc Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:16:16 +0100 Subject: [PATCH 20/49] [Security Solution][Detection Engine] updates ES|QL info popover (#168611) ## Summary - addresses https://github.com/elastic/ux-writing/issues/41 - content of info tooltip is moved to doc page. Instead only link to that page is displayed ### Before Screenshot 2023-10-13 at 15 56 20 ### After Screenshot 2023-10-13 at 15 53 07 ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Nastasha Solomon <79124755+nastasha-solomon@users.noreply.github.com> --- packages/kbn-doc-links/src/get_doc_links.ts | 4 +-- packages/kbn-doc-links/src/types.ts | 4 +-- .../components/esql_info_icon/index.tsx | 29 ++++++++------- .../components/esql_info_icon/translations.ts | 36 ------------------- .../rule_creation/logic/translations.ts | 2 +- .../rules/select_rule_type/translations.ts | 3 +- .../rule_creation/esql_rule_ess.cy.ts | 4 +-- 7 files changed, 23 insertions(+), 59 deletions(-) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 1f668ce162728..b8d46bad40cc4 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -457,6 +457,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { }, privileges: `${SECURITY_SOLUTION_DOCS}endpoint-management-req.html`, manageDetectionRules: `${SECURITY_SOLUTION_DOCS}rules-ui-management.html`, + createEsqlRuleType: `${SECURITY_SOLUTION_DOCS}rules-ui-create.html#create-esql-rule`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, @@ -856,9 +857,6 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { synthetics: { featureRoles: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-feature-roles.html`, }, - esql: { - statsBy: `${ELASTICSEARCH_DOCS}esql.html`, - }, telemetry: { settings: `${KIBANA_DOCS}telemetry-settings-kbn.html`, }, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 555167bf8574e..adc7f13c6c612 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -348,6 +348,7 @@ export interface DocLinks { }; readonly privileges: string; readonly manageDetectionRules: string; + readonly createEsqlRuleType: string; }; readonly query: { readonly eql: string; @@ -613,9 +614,6 @@ export interface DocLinks { readonly synthetics: { readonly featureRoles: string; }; - readonly esql: { - readonly statsBy: string; - }; readonly telemetry: { readonly settings: string; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/index.tsx index 0130b8eed78b8..d0b4cee6752ad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/index.tsx @@ -6,16 +6,13 @@ */ import React from 'react'; -import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui'; -import { Markdown } from '@kbn/kibana-react-plugin/public'; +import { EuiPopover, EuiText, EuiButtonIcon, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import * as i18n from './translations'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; - import { useKibana } from '../../../../common/lib/kibana'; -const POPOVER_WIDTH = 640; - /** * Icon and popover that gives hint to users how to get started with ES|QL rules */ @@ -30,14 +27,20 @@ const EsqlInfoIconComponent = () => { return ( - - + + + + ), + }} /> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/translations.ts index db5f8ea86ce63..8729f7b0dd3bc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_info_icon/translations.ts @@ -13,39 +13,3 @@ export const ARIA_LABEL = i18n.translate( defaultMessage: `Open help popover`, } ); - -export const getTooltipContent = (statsByLink: string, startUsingEsqlLink: string) => - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent', - { - defaultMessage: ` -### Aggregating rule -Is a rule that uses {statsByLink} grouping commands. So, its result can not be matched with a particular document in ES. -\`\`\` -FROM logs* -| STATS count = COUNT(host.name) BY host.name -| SORT host.name -\`\`\` - - -### Non-aggregating rule -Is a rule that does not use {statsByLink} grouping commands. Hence, each row in result can be tracked to a source document in ES. For this type of rule, -please use operator \`[metadata _id, _index, _version]\` after defining index source. This would allow deduplicate alerts and link them with the source document. - -Example - -\`\`\` -FROM logs* [metadata _id, _index, _version] -| WHERE event.id == "test" -| LIMIT 10 -\`\`\` - -Please, ensure, metadata properties \`id\`, \`_index\`, \`_version\` are carried over through pipe operators. - `, - values: { - statsByLink: `[STATS..BY](${statsByLink})`, - // Docs team will provide actual link to a new page before release - // startUsingEsqlLink: `[WIP: Get started using ES|QL rules](${startUsingEsqlLink})`, - }, - } - ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts index e0bbcd45b1f1a..bbb00053cdfab 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts @@ -23,6 +23,6 @@ export const esqlValidationErrorMessage = (message: string) => export const ESQL_VALIDATION_MISSING_ID_IN_QUERY_ERROR = i18n.translate( 'xpack.securitySolution.detectionEngine.esqlValidation.missingIdInQueryError', { - defaultMessage: `For non-aggregating rules(that don't use STATS..BY function), please write query that returns _id field from [metadata _id, _version, _index] operator`, + defaultMessage: `Queries that don’t use the STATS...BY function (non-aggregating queries) must include the [metadata _id, _version, _index] operator after the source command. For example: FROM logs* [metadata _id, _version, _index]. In addition, the metadata properties (_id, _version, and _index) must be returned in the query response.`, } ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts index cfd62ff3d57da..d8b61de136865 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts @@ -32,7 +32,8 @@ export const ESQL_TYPE_TITLE = i18n.translate( export const ESQL_TYPE_DESCRIPTION = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.esqlTypeDescription', { - defaultMessage: 'Use The Elasticsearch Query Language (ES|QL) to search or aggregate events', + defaultMessage: + 'Use Elasticsearch Query Language (ES|QL) to find events and aggregate search results.', } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts index 254deb99f3259..a22d24a9fd537 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/esql_rule_ess.cy.ts @@ -125,7 +125,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains( - 'write query that returns _id field from [metadata _id, _version, _index] operator' + 'must include the [metadata _id, _version, _index] operator after the source command' ); }); @@ -139,7 +139,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains( - 'write query that returns _id field from [metadata _id, _version, _index] operator' + 'must include the [metadata _id, _version, _index] operator after the source command' ); }); From 6037805fb068c8dc703999f656b110e986279614 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 18 Oct 2023 11:32:29 +0300 Subject: [PATCH 21/49] [ES|QL] Force ES client timeout after 2 minutes (#168929) ## Summary We found out that for ES|QL queries while the kibana server (and browser) timeouts in 2 minutes, the requests are still running in ES. So instead of being aborted in 2 minutes, the hit a 5minute timeout from the proxy which retries 3 times and then aborts. This happens ONLY when bfetch is enabled (which is the default) After an investigation with Rudolf it seems that we don't abort correctly in bsearch. Lukas is fixing it here https://github.com/elastic/kibana/pull/169041 . --- .../esql_search/esql_search_strategy.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts index 7f3f6f521853d..2af032826189f 100644 --- a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts @@ -11,6 +11,8 @@ import type { Logger } from '@kbn/core/server'; import { getKbnServerError, KbnServerError } from '@kbn/kibana-utils-plugin/server'; import type { ISearchStrategy } from '../../types'; +const ES_TIMEOUT_IN_MS = 120000; + export const esqlSearchStrategyProvider = ( logger: Logger, useInternalUser: boolean = false @@ -23,6 +25,17 @@ export const esqlSearchStrategyProvider = ( * @returns `Observable>` */ search: (request, { abortSignal, ...options }, { esClient, uiSettingsClient }) => { + const abortController = new AbortController(); + // We found out that there are cases where we are not aborting correctly + // For this reasons we want to manually cancel he abort signal after 2 mins + + abortSignal?.addEventListener('abort', () => { + abortController.abort(); + }); + + // Also abort after two mins + setTimeout(() => abortController.abort(), ES_TIMEOUT_IN_MS); + // Only default index pattern type is supported here. // See ese for other type support. if (request.indexType) { @@ -41,8 +54,10 @@ export const esqlSearchStrategyProvider = ( }, }, { - signal: abortSignal, + signal: abortController.signal, meta: true, + // we don't want the ES client to retry (default value is 3) + maxRetries: 0, } ); return { From 7a6826be3f3352944f887591bb439d9dd5ed9ed2 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 18 Oct 2023 10:42:44 +0200 Subject: [PATCH 22/49] [Synthetics] Service sync refactoring (#168587) Co-authored-by: Justin Kambic --- .../synthetics_service/service_api_client.ts | 34 ++- .../synthetics_service.test.ts | 168 ++++++++--- .../synthetics_service/synthetics_service.ts | 276 ++++++++---------- .../server/synthetics_service/utils/mocks.ts | 67 +++-- 4 files changed, 331 insertions(+), 214 deletions(-) diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts index 02779526c8421..e30b9a18872a9 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts @@ -18,7 +18,12 @@ import { DataStreamConfig, } from './formatters/public_formatters/convert_to_data_stream'; import { sendErrorTelemetryEvents } from '../routes/telemetry/monitor_upgrade_sender'; -import { MonitorFields, PublicLocations, ServiceLocationErrors } from '../../common/runtime_types'; +import { + MonitorFields, + PublicLocations, + ServiceLocation, + ServiceLocationErrors, +} from '../../common/runtime_types'; import { ServiceConfig } from '../../common/config'; const TEST_SERVICE_USERNAME = 'localKibanaIntegrationTestsUser'; @@ -32,6 +37,7 @@ export interface ServiceData { endpoint?: 'monitors' | 'runOnce' | 'sync'; isEdit?: boolean; license: LicenseGetLicenseInformation; + location?: ServiceLocation; } export interface ServicePayload { @@ -161,10 +167,14 @@ export class ServiceAPIClient { } async syncMonitors(data: ServiceData) { - return (await this.callAPI('PUT', { ...data, endpoint: 'sync' })).pushErrors; + try { + return (await this.callAPI('PUT', { ...data, endpoint: 'sync' })).pushErrors; + } catch (e) { + this.logger.error(e); + } } - processServiceData({ monitors, ...restOfData }: ServiceData) { + processServiceData({ monitors, location, ...restOfData }: ServiceData) { // group monitors by location const monitorsByLocation: Array<{ location: { id: string; url: string }; @@ -172,12 +182,14 @@ export class ServiceAPIClient { data: ServicePayload; }> = []; this.locations.forEach(({ id, url }) => { - const locMonitors = monitors.filter(({ locations }) => - locations?.find((loc) => loc.id === id && loc.isServiceManaged) - ); - if (locMonitors.length > 0) { - const data = this.getRequestData({ ...restOfData, monitors: locMonitors }); - monitorsByLocation.push({ location: { id, url }, monitors: locMonitors, data }); + if (!location || location.id === id) { + const locMonitors = monitors.filter(({ locations }) => + locations?.find((loc) => loc.id === id && loc.isServiceManaged) + ); + if (locMonitors.length > 0) { + const data = this.getRequestData({ ...restOfData, monitors: locMonitors }); + monitorsByLocation.push({ location: { id, url }, monitors: locMonitors, data }); + } } }); return monitorsByLocation; @@ -275,7 +287,9 @@ export class ServiceAPIClient { result: AxiosResponse | ServicePayload ) { if ('status' in result || 'request' in result) { - this.logger.debug(result.data); + if (result.data) { + this.logger.debug(result.data); + } this.logger.debug( `Successfully called service location ${url}${result.request?.path} with method ${method} with ${numMonitors} monitors` ); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts index e98508a5e4116..66c25a8bb5ca1 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -23,22 +23,24 @@ const taskManagerSetup = taskManagerMock.createSetup(); const mockCoreStart = coreMock.createStart() as CoreStart; -mockCoreStart.elasticsearch.client.asInternalUser.license.get = jest.fn().mockResolvedValue({ - license: { - status: 'active', - uid: 'c5788419-1c6f-424a-9217-da7a0a9151a0', - type: 'platinum', - issue_date: '2022-11-29T00:00:00.000Z', - issue_date_in_millis: 1669680000000, - expiry_date: '2024-12-31T23:59:59.999Z', - expiry_date_in_millis: 1735689599999, - max_nodes: 100, - max_resource_units: null, - issued_to: 'Elastic - INTERNAL (development environments)', - issuer: 'API', - start_date_in_millis: 1669680000000, - }, -}); +const mockLicense = () => { + mockCoreStart.elasticsearch.client.asInternalUser.license.get = jest.fn().mockResolvedValue({ + license: { + status: 'active', + uid: 'c5788419-1c6f-424a-9217-da7a0a9151a0', + type: 'platinum', + issue_date: '2022-11-29T00:00:00.000Z', + issue_date_in_millis: 1669680000000, + expiry_date: '2024-12-31T23:59:59.999Z', + expiry_date_in_millis: 1735689599999, + max_nodes: 100, + max_resource_units: null, + issued_to: 'Elastic - INTERNAL (development environments)', + issuer: 'API', + start_date_in_millis: 1669680000000, + }, + }); +}; const getFakePayload = (locations: HeartbeatConfig['locations']) => { return { @@ -87,6 +89,16 @@ describe('SyntheticsService', () => { savedObjectsClient: savedObjectsClientMock.create()!, } as unknown as SyntheticsServerSetup; + const mockConfig = { + service: { + devUrl: 'http://localhost', + manifestUrl: 'https://test-manifest.com', + }, + enabled: true, + }; + + mockLicense(); + const getMockedService = (locationsNum: number = 1) => { const locations = times(locationsNum).map((n) => { return { @@ -101,13 +113,7 @@ describe('SyntheticsService', () => { status: LocationStatus.GA, }; }); - serverMock.config = { - service: { - devUrl: 'http://localhost', - manifestUrl: 'https://test-manifest.com', - }, - enabled: true, - }; + serverMock.config = mockConfig; if (serverMock.savedObjectsClient) { serverMock.savedObjectsClient.find = jest.fn().mockResolvedValue({ saved_objects: [ @@ -133,8 +139,10 @@ describe('SyntheticsService', () => { const service = new SyntheticsService(serverMock); service.apiClient.locations = locations; + service.locations = locations; jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' }); + jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({}); return { service, locations }; }; @@ -222,7 +230,7 @@ describe('SyntheticsService', () => { const { service } = getMockedService(); jest.spyOn(service, 'getOutput').mockRestore(); - serverMock.encryptedSavedObjects = mockEncryptedSO(null) as any; + serverMock.encryptedSavedObjects = mockEncryptedSO(); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); @@ -240,8 +248,12 @@ describe('SyntheticsService', () => { jest.spyOn(service, 'getOutput').mockRestore(); serverMock.encryptedSavedObjects = mockEncryptedSO({ - attributes: getFakePayload([locations[0]]), - }) as any; + monitors: [ + { + attributes: getFakePayload([locations[0]]), + }, + ], + }); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); @@ -309,8 +321,12 @@ describe('SyntheticsService', () => { const { service, locations } = getMockedService(); serverMock.encryptedSavedObjects = mockEncryptedSO({ - attributes: getFakePayload([locations[0]]), - }) as any; + monitors: [ + { + attributes: getFakePayload([locations[0]]), + }, + ], + }); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); @@ -357,8 +373,10 @@ describe('SyntheticsService', () => { }); serverMock.encryptedSavedObjects = mockEncryptedSO({ - attributes: getFakePayload([locations[0]]), - }) as any; + monitors: { + attributes: getFakePayload([locations[0]]), + }, + }); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); @@ -370,13 +388,18 @@ describe('SyntheticsService', () => { describe('getSyntheticsParams', () => { it('returns the params for all spaces', async () => { const { service } = getMockedService(); + jest.spyOn(service, 'getSyntheticsParams').mockReset(); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); serverMock.encryptedSavedObjects = mockEncryptedSO({ - attributes: { key: 'username', value: 'elastic' }, - namespaces: ['*'], - }) as any; + params: [ + { + attributes: { key: 'username', value: 'elastic' }, + namespaces: ['*'], + }, + ], + }); const params = await service.getSyntheticsParams(); @@ -389,6 +412,16 @@ describe('SyntheticsService', () => { it('returns the params for specific space', async () => { const { service } = getMockedService(); + jest.spyOn(service, 'getSyntheticsParams').mockReset(); + + serverMock.encryptedSavedObjects = mockEncryptedSO({ + params: [ + { + attributes: { key: 'username', value: 'elastic' }, + namespaces: ['*'], + }, + ], + }); const params = await service.getSyntheticsParams({ spaceId: 'default' }); @@ -403,11 +436,16 @@ describe('SyntheticsService', () => { }); it('returns the space limited params', async () => { const { service } = getMockedService(); + jest.spyOn(service, 'getSyntheticsParams').mockReset(); serverMock.encryptedSavedObjects = mockEncryptedSO({ - attributes: { key: 'username', value: 'elastic' }, - namespaces: ['default'], - }) as any; + params: [ + { + attributes: { key: 'username', value: 'elastic' }, + namespaces: ['default'], + }, + ], + }); const params = await service.getSyntheticsParams({ spaceId: 'default' }); @@ -418,4 +456,62 @@ describe('SyntheticsService', () => { }); }); }); + + describe('pagination', () => { + const service = new SyntheticsService(serverMock); + + const locations = times(5).map((n) => { + return { + id: `loc-${n}`, + label: `Location ${n}`, + url: `https://example.com/${n}`, + geo: { + lat: 0, + lon: 0, + }, + isServiceManaged: true, + status: LocationStatus.GA, + }; + }); + service.apiClient.locations = locations; + service.locations = locations; + jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' }); + jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({}); + + it('paginates the results', async () => { + serverMock.config = mockConfig; + + mockLicense(); + + const syncSpy = jest.spyOn(service.apiClient, 'syncMonitors'); + + let num = -1; + const data = times(10000).map((n) => { + if (num === 4) { + num = -1; + } + num++; + if (locations?.[num + 1]) { + return { + attributes: getFakePayload([locations[num], locations[num + 1]]), + }; + } + return { + attributes: getFakePayload([locations[num]]), + }; + }); + + serverMock.encryptedSavedObjects = mockEncryptedSO({ monitors: data }); + + (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); + + await service.pushConfigs(); + + expect(syncSpy).toHaveBeenCalledTimes(72); + expect(axios).toHaveBeenCalledTimes(72); + expect(logger.debug).toHaveBeenCalledTimes(112); + expect(logger.info).toHaveBeenCalledTimes(0); + expect(logger.error).toHaveBeenCalledTimes(0); + }); + }); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index da0434201eaa2..4f5739e933e0a 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -7,18 +7,16 @@ /* eslint-disable max-classes-per-file */ -import { Logger, SavedObject, ElasticsearchClient } from '@kbn/core/server'; +import { ElasticsearchClient, Logger, SavedObject } from '@kbn/core/server'; import { ConcreteTaskInstance, TaskInstance, TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { concatMap, Subject } from 'rxjs'; -import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; -import pMap from 'p-map'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants'; +import pMap from 'p-map'; import { registerCleanUpTask } from './private_location/clean_up_task'; import { SyntheticsServerSetup } from '../types'; import { syntheticsMonitorType, syntheticsParamType } from '../../common/types/saved_objects'; @@ -31,11 +29,9 @@ import { ServiceAPIClient, ServiceData } from './service_api_client'; import { ConfigKey, - EncryptedSyntheticsMonitorAttributes, MonitorFields, ServiceLocationErrors, ServiceLocations, - SyntheticsMonitorWithId, SyntheticsMonitorWithSecretsAttributes, SyntheticsParams, ThrottlingOptions, @@ -279,6 +275,18 @@ export class SyntheticsService { return license; } + private async getSOClientFinder({ pageSize }: { pageSize: number }) { + const encryptedClient = this.server.encryptedSavedObjects.getClient(); + + return await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser( + { + type: syntheticsMonitorType, + perPage: pageSize, + namespaces: [ALL_SPACES_ID], + } + ); + } + private getESClient() { if (!this.server.coreStart) { return; @@ -373,55 +381,95 @@ export class SyntheticsService { async pushConfigs() { const license = await this.getLicense(); const service = this; - const subject = new Subject(); + + const PER_PAGE = 250; + service.syncErrors = []; let output: ServiceData['output'] | null = null; - subject - .pipe( - concatMap(async (monitors) => { - try { - if (monitors.length === 0 || !this.config.manifestUrl) { - return; - } + const paramsBySpace = await this.getSyntheticsParams(); + const finder = await this.getSOClientFinder({ pageSize: PER_PAGE }); - if (!output) { - output = await this.getOutput(); + const bucketsByLocation: Record = {}; + this.locations.forEach((location) => { + bucketsByLocation[location.id] = []; + }); - if (!output) { - sendErrorTelemetryEvents(service.logger, service.server.telemetry, { - reason: 'API key is not valid.', - message: 'Failed to push configs. API key is not valid.', - type: 'invalidApiKey', - stackVersion: service.server.stackVersion, - }); - return; - } - } + const syncAllLocations = async (perBucket = 0) => { + await pMap( + this.locations, + async (location) => { + if (bucketsByLocation[location.id].length > perBucket && output) { + const locMonitors = bucketsByLocation[location.id].splice(0, PER_PAGE); - this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); + this.logger.debug( + `${locMonitors.length} monitors will be pushed to synthetics service for location ${location.id}.` + ); - service.syncErrors = await this.apiClient.syncMonitors({ - monitors, + const syncErrors = await this.apiClient.syncMonitors({ + monitors: locMonitors, output, license, + location, }); - } catch (e) { + + this.syncErrors = [...(this.syncErrors ?? []), ...(syncErrors ?? [])]; + } + }, + { + stopOnError: false, + } + ); + }; + + for await (const result of finder.find()) { + try { + if (!output) { + output = await this.getOutput(); + if (!output) { sendErrorTelemetryEvents(service.logger, service.server.telemetry, { - reason: 'Failed to push configs to service', - message: e?.message, - type: 'pushConfigsError', - code: e?.code, - status: e.status, + reason: 'API key is not valid.', + message: 'Failed to push configs. API key is not valid.', + type: 'invalidApiKey', stackVersion: service.server.stackVersion, }); - this.logger.error(e); + return; } - }) - ) - .subscribe(); + } + + const monitors = result.saved_objects.filter(({ error }) => !error); + const formattedConfigs = this.normalizeConfigs(monitors, paramsBySpace); + + this.logger.debug( + `${formattedConfigs.length} monitors will be pushed to synthetics service.` + ); + + formattedConfigs.forEach((monitor) => { + monitor.locations.forEach((location) => { + if (location.isServiceManaged) { + bucketsByLocation[location.id]?.push(monitor); + } + }); + }); + + await syncAllLocations(PER_PAGE); + } catch (e) { + sendErrorTelemetryEvents(service.logger, service.server.telemetry, { + reason: 'Failed to push configs to service', + message: e?.message, + type: 'pushConfigsError', + code: e?.code, + status: e.status, + stackVersion: service.server.stackVersion, + }); + this.logger.error(e); + } + } - await this.getMonitorConfigs(subject); + // execute the remaining monitors + await syncAllLocations(); + + await finder.close(); } async runOnceConfigs(configs?: ConfigData) { @@ -481,123 +529,28 @@ export class SyntheticsService { async deleteAllConfigs() { const license = await this.getLicense(); - const subject = new Subject(); - - subject - .pipe( - concatMap(async (monitors) => { - const hasPublicLocations = monitors.some((config) => - config.locations.some(({ isServiceManaged }) => isServiceManaged) - ); - - if (hasPublicLocations) { - const output = await this.getOutput(); - if (!output) { - return; - } - - const data = { - output, - monitors, - license, - }; - return await this.apiClient.delete(data); - } - }) - ) - .subscribe(); - - await this.getMonitorConfigs(subject); - } - - async getMonitorConfigs(subject: Subject) { - const soClient = this.server.savedObjectsClient; - const encryptedClient = this.server.encryptedSavedObjects.getClient(); - - if (!soClient?.find) { - return [] as SyntheticsMonitorWithId[]; - } - const paramsBySpace = await this.getSyntheticsParams(); - - const finder = soClient.createPointInTimeFinder({ - type: syntheticsMonitorType, - perPage: 100, - namespaces: [ALL_SPACES_ID], - }); + const finder = await this.getSOClientFinder({ pageSize: 100 }); + const output = await this.getOutput(); + if (!output) { + return; + } for await (const result of finder.find()) { - const monitors = await this.decryptMonitors(result.saved_objects, encryptedClient); - - const configDataList: ConfigData[] = (monitors ?? []).map((monitor) => { - const attributes = monitor.attributes as unknown as MonitorFields; - const monitorSpace = monitor.namespaces?.[0] ?? DEFAULT_SPACE_ID; - - const params = paramsBySpace[monitorSpace]; + const monitors = this.normalizeConfigs(result.saved_objects, paramsBySpace); + const hasPublicLocations = monitors.some((config) => + config.locations.some(({ isServiceManaged }) => isServiceManaged) + ); - return { - params: { ...params, ...(paramsBySpace?.[ALL_SPACES_ID] ?? {}) }, - monitor: normalizeSecrets(monitor).attributes, - configId: monitor.id, - heartbeatId: attributes[ConfigKey.MONITOR_QUERY_ID], + if (hasPublicLocations) { + const data = { + output, + monitors, + license, }; - }); - - const formattedConfigs = this.formatConfigs(configDataList); - - subject.next(formattedConfigs as MonitorFields[]); + return await this.apiClient.delete(data); + } } - - await finder.close(); - } - - async decryptMonitors( - monitors: Array>, - encryptedClient: EncryptedSavedObjectsClient - ) { - const start = performance.now(); - - const decryptedMonitors = await pMap( - monitors, - (monitor) => - new Promise((resolve) => { - encryptedClient - .getDecryptedAsInternalUser( - syntheticsMonitorType, - monitor.id, - { - namespace: monitor.namespaces?.[0], - } - ) - .then((decryptedMonitor) => resolve(decryptedMonitor)) - .catch((e) => { - this.logger.error(e); - sendErrorTelemetryEvents(this.logger, this.server.telemetry, { - reason: 'Failed to decrypt monitor', - message: e?.message, - type: 'runTaskError', - code: e?.code, - status: e.status, - stackVersion: this.server.stackVersion, - }); - resolve(null); - }); - }) - ); - - const end = performance.now(); - const duration = end - start; - - this.logger.debug(`Decrypted ${monitors.length} monitors. Took ${duration} milliseconds`, { - event: { - duration, - }, - monitors: monitors.length, - }); - - return decryptedMonitors.filter((monitor) => monitor !== null) as Array< - SavedObject - >; } async getSyntheticsParams({ @@ -671,6 +624,27 @@ export class SyntheticsService { ); }); } + + normalizeConfigs( + monitors: Array>, + paramsBySpace: Record> + ) { + const configDataList = (monitors ?? []).map((monitor) => { + const attributes = monitor.attributes as unknown as MonitorFields; + const monitorSpace = monitor.namespaces?.[0] ?? DEFAULT_SPACE_ID; + + const params = paramsBySpace[monitorSpace]; + + return { + params: { ...params, ...(paramsBySpace?.[ALL_SPACES_ID] ?? {}) }, + monitor: normalizeSecrets(monitor).attributes, + configId: monitor.id, + heartbeatId: attributes[ConfigKey.MONITOR_QUERY_ID], + }; + }); + + return this.formatConfigs(configDataList) as MonitorFields[]; + } } class IndexTemplateInstallationError extends Error { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/utils/mocks.ts b/x-pack/plugins/synthetics/server/synthetics_service/utils/mocks.ts index f881d242aeb52..b9d6b54961f59 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/utils/mocks.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/utils/mocks.ts @@ -6,21 +6,54 @@ */ import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; +import { cloneDeep } from 'lodash'; +import { syntheticsParamType } from '../../../common/types/saved_objects'; -export const mockEncryptedSO = ( - data: any = { attributes: { key: 'username', value: 'elastic' }, namespaces: ['*'] } -) => ({ - getClient: jest.fn().mockReturnValue({ - getDecryptedAsInternalUser: jest.fn().mockResolvedValue(data), - createPointInTimeFinderDecryptedAsInternalUser: jest.fn().mockImplementation(() => ({ - close: jest.fn(), - find: jest.fn().mockReturnValue({ - async *[Symbol.asyncIterator]() { - yield { - saved_objects: data === null ? [] : [data], - }; - }, - }), - })), - } as jest.Mocked), -}); +export const mockEncryptedSO = ({ + monitors = null, + params, +}: { monitors?: any; params?: any } = {}) => { + const result = cloneDeep(monitors); + const mockParams = params ?? [ + { attributes: { key: 'username', value: 'elastic' }, namespaces: ['*'] }, + ]; + return { + isEncryptionError: jest.fn(), + getClient: jest.fn().mockReturnValue({ + getDecryptedAsInternalUser: jest.fn().mockResolvedValue(monitors), + createPointInTimeFinderDecryptedAsInternalUser: jest + .fn() + .mockImplementation(({ perPage, type: soType }) => ({ + close: jest.fn(), + find: jest.fn().mockReturnValue({ + async *[Symbol.asyncIterator]() { + if (soType === syntheticsParamType) { + yield { + saved_objects: mockParams, + }; + return; + } + if (!perPage) { + yield { + saved_objects: result, + }; + return; + } + if (monitors === null) { + return; + } + do { + const currentPage = result.splice(0, perPage); + if (currentPage.length === 0) { + return; + } + yield { + saved_objects: currentPage, + }; + } while (result.length > 0); + }, + }), + })), + } as jest.Mocked), + }; +}; From 2ebb325d24cef862295008e881c11367e0c00519 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 18 Oct 2023 12:43:50 +0300 Subject: [PATCH 23/49] [ES|QL] Do not refresh the query when a user clicks the warning/error popover (#169193) ## Summary Leftover from my ES|QL implementation. We don't need to rerun the query when a user clicks the warning popover. I am removing this here. Note: I still keep it on the footer component because I will need this here https://github.com/elastic/kibana/pull/167754 --- packages/kbn-text-based-editor/src/editor_footer.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/kbn-text-based-editor/src/editor_footer.tsx b/packages/kbn-text-based-editor/src/editor_footer.tsx index f89a14d06f106..5070e2d5789e7 100644 --- a/packages/kbn-text-based-editor/src/editor_footer.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer.tsx @@ -58,14 +58,12 @@ export function ErrorsWarningsPopover({ isPopoverOpen, items, type, - refreshErrors, setIsPopoverOpen, onErrorClick, }: { isPopoverOpen: boolean; items: MonacoError[]; type: 'error' | 'warning'; - refreshErrors: () => void; setIsPopoverOpen: (flag: boolean) => void; onErrorClick: (error: MonacoError) => void; }) { @@ -89,7 +87,6 @@ export function ErrorsWarningsPopover({ } `} onClick={() => { - refreshErrors(); setIsPopoverOpen(!isPopoverOpen); }} > @@ -184,7 +181,6 @@ export const EditorFooter = memo(function EditorFooter({ isPopoverOpen={isPopoverOpen} items={errors} type="error" - refreshErrors={refreshErrors} setIsPopoverOpen={setIsPopoverOpen} onErrorClick={onErrorClick} /> @@ -194,7 +190,6 @@ export const EditorFooter = memo(function EditorFooter({ isPopoverOpen={isPopoverOpen} items={warning} type="warning" - refreshErrors={refreshErrors} setIsPopoverOpen={setIsPopoverOpen} onErrorClick={onErrorClick} /> From 09bda6a4a5000033d7a10f333ce4325a45537150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Wed, 18 Oct 2023 10:45:08 +0100 Subject: [PATCH 24/49] [Serverless] Add panels to side nav (#167774) --- .../src/chrome_service.tsx | 5 + .../src/ui/project/app_menu.tsx | 6 +- .../src/ui/project/header.test.tsx | 1 + .../src/ui/project/header.tsx | 4 +- .../src/ui/project/navigation.tsx | 21 +- .../src/chrome_service.mock.ts | 1 + .../core/chrome/core-chrome-browser/index.ts | 2 + .../core-chrome-browser/src/contracts.ts | 5 + .../chrome/core-chrome-browser/src/index.ts | 2 + .../src/project_navigation.ts | 152 ++- packages/shared-ux/chrome/navigation/index.ts | 4 + .../chrome/navigation/mocks/src/jest.ts | 1 + .../chrome/navigation/mocks/src/storybook.ts | 1 + .../chrome/navigation/src/services.tsx | 3 + .../default_navigation.test.tsx.snap | 74 +- .../navigation/src/ui/components/index.ts | 1 + .../src/ui/components/navigation.test.tsx | 554 +++++++---- .../src/ui/components/navigation.tsx | 30 +- .../src/ui/components/navigation_bucket.tsx | 11 +- .../src/ui/components/navigation_group.tsx | 48 +- .../src/ui/components/navigation_item.tsx | 62 +- .../components/navigation_item_open_panel.tsx | 113 +++ .../ui/components/navigation_section_ui.tsx | 292 +++--- .../src/ui/components/navigation_ui.tsx | 6 + .../src/ui/components/panel/context.tsx | 97 ++ .../ui/components/panel/default_content.tsx | 120 +++ .../src/ui/components/panel/index.ts | 13 + .../src/ui/components/panel/label_badge.tsx | 40 + .../ui/components/panel/navigation_panel.tsx | 69 ++ .../src/ui/components/panel/panel_group.tsx | 126 +++ .../ui/components/panel/panel_nav_item.tsx | 52 + .../components/panel/panel_nav_item_label.tsx | 24 + .../src/ui/components/panel/styles.ts | 46 + .../src/ui/components/panel/types.ts | 33 + .../src/ui/default_navigation.test.tsx | 28 + .../navigation/src/ui/default_navigation.tsx | 153 ++- .../src/ui/hooks/use_init_navnode.ts | 90 +- .../chrome/navigation/src/ui/index.ts | 3 + .../navigation/src/ui/navigation.stories.tsx | 889 ++++++++++++++++-- .../chrome/navigation/src/ui/types.ts | 66 +- .../shared-ux/chrome/navigation/src/utils.ts | 41 + .../chrome/navigation/types/index.ts | 2 + .../navigation_tree/navigation_tree.ts | 142 +-- .../components/side_navigation/index.tsx | 4 +- .../serverless_search/public/layout/nav.tsx | 2 +- .../page_objects/svl_common_navigation.ts | 16 +- .../services/ml/observability_navigation.ts | 8 +- .../cypress/e2e/navigation.cy.ts | 63 +- .../test_suites/observability/navigation.ts | 6 +- 49 files changed, 2851 insertions(+), 681 deletions(-) create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/context.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/default_content.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/index.ts create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/label_badge.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_group.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item_label.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/styles.ts create mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index bf44390d13294..7cd8cec951a3e 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -80,6 +80,7 @@ export class ChromeService { private readonly docTitle = new DocTitleService(); private readonly projectNavigation = new ProjectNavigationService(); private mutationObserver: MutationObserver | undefined; + private readonly isSideNavCollapsed$ = new BehaviorSubject(true); constructor(private readonly params: ConstructorParams) {} @@ -386,6 +387,9 @@ export class ChromeService { docLinks={docLinks} kibanaVersion={injectedMetadata.getKibanaVersion()} prependBasePath={http.basePath.prepend} + toggleSideNav={(isCollapsed) => { + this.isSideNavCollapsed$.next(isCollapsed); + }} > @@ -508,6 +512,7 @@ export class ChromeService { getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)), setChromeStyle, getChromeStyle$: () => chromeStyle$.pipe(takeUntil(this.stop$)), + getIsSideNavCollapsed$: () => this.isSideNavCollapsed$.asObservable(), project: { setHome: setProjectHome, setProjectsUrl, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx index 0c0e4dbdf9167..5c939752250ae 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx @@ -17,13 +17,17 @@ interface AppMenuBarProps { } export const AppMenuBar = ({ headerActionMenuMounter }: AppMenuBarProps) => { const { euiTheme } = useEuiTheme(); + const zIndex = + typeof euiTheme.levels.header === 'number' + ? euiTheme.levels.header - 1 // We want it to appear right below the header + : euiTheme.levels.header; return (
{ navControlsCenter$: Rx.of([]), navControlsRight$: Rx.of([]), prependBasePath: (str) => `hello/world/${str}`, + toggleSideNav: jest.fn(), }; it('renders', async () => { diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx index 8767c1f9c53f7..7ac11ecb5bc54 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -110,6 +110,7 @@ export interface Props { navControlsCenter$: Observable; navControlsRight$: Observable; prependBasePath: (url: string) => string; + toggleSideNav: (isCollapsed: boolean) => void; } const LOADING_DEBOUNCE_TIME = 80; @@ -172,6 +173,7 @@ export const ProjectHeader = ({ children, prependBasePath, docLinks, + toggleSideNav, ...observables }: Props) => { const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$); @@ -196,7 +198,7 @@ export const ProjectHeader = ({ - {children} + {children} diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx index adc6b3f28c2e5..b8eea90c71c23 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx @@ -6,24 +6,39 @@ * Side Public License, v 1. */ +import React, { useEffect, useRef } from 'react'; import { EuiCollapsibleNavBeta } from '@elastic/eui'; -import React from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; const LOCAL_STORAGE_IS_COLLAPSED_KEY = 'PROJECT_NAVIGATION_COLLAPSED' as const; -export const ProjectNavigation: React.FC = ({ children }) => { +export const ProjectNavigation: React.FC<{ + toggleSideNav: (isVisible: boolean) => void; +}> = ({ children, toggleSideNav }) => { + const isMounted = useRef(false); const [isCollapsed, setIsCollapsed] = useLocalStorage(LOCAL_STORAGE_IS_COLLAPSED_KEY, false); const onCollapseToggle = (nextIsCollapsed: boolean) => { setIsCollapsed(nextIsCollapsed); + toggleSideNav(nextIsCollapsed); }; + useEffect(() => { + if (!isMounted.current && isCollapsed !== undefined) { + toggleSideNav(isCollapsed); + } + isMounted.current = true; + }, [isCollapsed, toggleSideNav]); + return ( {children} diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 225263674e595..d6a7a2e065945 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -50,6 +50,7 @@ const createStartContractMock = () => { setBadge: jest.fn(), getBreadcrumbs$: jest.fn(), setBreadcrumbs: jest.fn(), + getIsSideNavCollapsed$: jest.fn(), getBreadcrumbsAppendExtension$: jest.fn(), setBreadcrumbsAppendExtension: jest.fn(), getGlobalHelpExtensionMenuLinks$: jest.fn(), diff --git a/packages/core/chrome/core-chrome-browser/index.ts b/packages/core/chrome/core-chrome-browser/index.ts index 2e587cbcda497..207864f16a844 100644 --- a/packages/core/chrome/core-chrome-browser/index.ts +++ b/packages/core/chrome/core-chrome-browser/index.ts @@ -37,8 +37,10 @@ export type { CloudLinkId, SideNavCompProps, SideNavComponent, + SideNavNodeStatus, ChromeProjectBreadcrumb, ChromeSetProjectBreadcrumbsParams, NodeDefinition, NodeDefinitionWithChildren, + NodeRenderAs, } from './src'; diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts index c9893ed3863ce..328c0142df9cc 100644 --- a/packages/core/chrome/core-chrome-browser/src/contracts.ts +++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts @@ -171,4 +171,9 @@ export interface ChromeStart { * Get an observable of the current style type of the chrome. */ getChromeStyle$(): Observable; + + /** + * Get an observable of the current collapsed state of the side nav. + */ + getIsSideNavCollapsed$(): Observable; } diff --git a/packages/core/chrome/core-chrome-browser/src/index.ts b/packages/core/chrome/core-chrome-browser/src/index.ts index b931f05ecd327..6c9a68dc75018 100644 --- a/packages/core/chrome/core-chrome-browser/src/index.ts +++ b/packages/core/chrome/core-chrome-browser/src/index.ts @@ -36,8 +36,10 @@ export type { CloudLinkId, SideNavCompProps, SideNavComponent, + SideNavNodeStatus, ChromeSetProjectBreadcrumbsParams, ChromeProjectBreadcrumb, NodeDefinition, NodeDefinitionWithChildren, + RenderAs as NodeRenderAs, } from './project_navigation'; diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts index 2d35272e43679..601e7391f4a0f 100644 --- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts +++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts @@ -8,7 +8,7 @@ import type { ComponentType } from 'react'; import type { Location } from 'history'; -import { EuiAccordionProps } from '@elastic/eui'; +import { EuiAccordionProps, IconType } from '@elastic/eui'; import type { AppId as DevToolsApp, DeepLinkId as DevToolsLink } from '@kbn/deeplinks-devtools'; import type { AppId as AnalyticsApp, @@ -49,6 +49,10 @@ export type AppDeepLinkId = /** @public */ export type CloudLinkId = 'userAndRoles' | 'performance' | 'billingAndSub' | 'deployment'; +export type SideNavNodeStatus = 'hidden' | 'visible'; + +export type RenderAs = 'block' | 'accordion' | 'panelOpener' | 'item'; + export type GetIsActiveFn = (params: { /** The current path name including the basePath + hash value but **without** any query params */ pathNameSerialized: string; @@ -58,8 +62,99 @@ export type GetIsActiveFn = (params: { prepend: (path: string) => string; }) => boolean; +/** + * Base definition of navigation nodes. A node can either be a "group" or an "item". + * Each have commmon properties and specific properties. + */ +interface NodeDefinitionBase { + /** + * Optional icon for the navigation node. Note: not all navigation depth will render the icon + */ + icon?: IconType; + /** + * href for absolute links only. Internal links should use "link". + */ + href?: string; + /** + * Optional status to indicate if the breadcrumb should be hidden when this node is active. + * @default 'visible' + */ + breadcrumbStatus?: 'hidden' | 'visible'; + /** + * Optional status to for the side navigation. "hidden" and "visible" are self explanatory. + * The `renderAsItem` status is _only_ for group nodes (nodes with children declared or with + * the "nodeType" set to `group`) and allow to render the node as an "item" instead of the head of + * a group. This is usefull to have sub-pages declared in the tree that will correctly be mapped + * in the Breadcrumbs, but are not rendered in the side navigation. + * @default 'visible' + */ + sideNavStatus?: SideNavNodeStatus; + /** + * Optional function to get the active state. This function is called whenever the location changes. + */ + getIsActive?: GetIsActiveFn; + /** + * ---------------------------------------------------------------------------------------------- + * ------------------------------- GROUP NODES ONLY PROPS --------------------------------------- + * ---------------------------------------------------------------------------------------------- + */ + /** + * ["group" nodes only] Optional flag to indicate if the node must be treated as a group title. + * Can not be used with `children` + */ + isGroupTitle?: boolean; + /** + * ["group" nodes only] Property to indicate how the group should be rendered. + * - Accordion: wraps the items in an EuiAccordion + * - PanelOpener: renders a button to open a panel on the right of the side nav + * - item: renders the group as an item in the side nav + * @default 'block' + */ + renderAs?: RenderAs; + /** + * ["group" nodes only] Optional flag to indicate if a horizontal rule should be rendered after the node. + * Note: this property is currently only used for (1) "group" nodes and (2) in the navigation + * panel opening on the right of the side nav. + */ + appendHorizontalRule?: boolean; + /** + * ["group" nodes only] Temp prop. Will be removed once the new navigation is fully implemented. + */ + accordionProps?: Partial; + /** + * ---------------------------------------------------------------------------------------------- + * -------------------------------- ITEM NODES ONLY PROPS --------------------------------------- + * ---------------------------------------------------------------------------------------------- + */ + /** + * ["item" nodes only] Optional flag to indicate if the target page should be opened in a new Browser tab. + * Note: this property is currently only used in the navigation panel opening on the right of the side nav. + */ + openInNewTab?: boolean; + /** + * ["item" nodes only] Optional flag to indicate if a badge should be rendered next to the text. + * Note: this property is currently only used in the navigation panel opening on the right of the side nav. + */ + withBadge?: boolean; + /** + * ["item" nodes only] If `withBadge` is true, this object can be used to customize the badge. + */ + badgeOptions?: { + /** The text of the badge. Default: "Beta" */ + text?: string; + }; +} + /** @public */ -export interface ChromeProjectNavigationNode { +/** + * Chrome project navigation node. This is the tree definition stored in the Chrome service + * that is generated based on the NodeDefinition below. + * Some of the process that occurs between the 2 are: + * - "link" prop get converted to existing ChromNavLink + * - "path" is added to each node based on where it is located in the tree + * - "isActive" state is set for each node if its URL matches the current location + */ +export interface ChromeProjectNavigationNode extends NodeDefinitionBase { /** Optional id, if not passed a "link" must be provided. */ id: string; /** Optional title. If not provided and a "link" is provided the title will be the Deep link title */ @@ -68,32 +163,15 @@ export interface ChromeProjectNavigationNode { path: string[]; /** App id or deeplink id */ deepLink?: ChromeNavLink; - /** Optional icon for the navigation node. Note: not all navigation depth will render the icon */ - icon?: string; - /** Optional flag to indicate if the node must be treated as a group title */ - isGroupTitle?: boolean; - /** Optional children of the navigation node */ - children?: ChromeProjectNavigationNode[]; /** - * href for absolute links only. Internal links should use "link". + * Optional children of the navigation node. Once a node has "children" defined it is + * considered a "group" node. */ - href?: string; + children?: ChromeProjectNavigationNode[]; /** * Flag to indicate if the node is currently active. */ isActive?: boolean; - /** - * Optional function to get the active state. This function is called whenever the location changes. - */ - getIsActive?: GetIsActiveFn; - - /** - * Optional flag to indicate if the breadcrumb should be hidden when this node is active. - * @default 'visible' - */ - breadcrumbStatus?: 'hidden' | 'visible'; - - accordionProps?: Partial; } /** @public */ @@ -120,7 +198,8 @@ export interface ChromeSetProjectBreadcrumbsParams { absolute: boolean; } -type NonEmptyArray = [T, ...T[]]; +// --- NOTE: The following types are the ones that the consumer uses to configure their navigation. +// --- They are converted to the ChromeProjectNavigationNode type above. /** * @public @@ -134,7 +213,7 @@ export interface NodeDefinition< LinkId extends AppDeepLinkId = AppDeepLinkId, Id extends string = string, ChildrenId extends string = Id -> { +> extends NodeDefinitionBase { /** Optional id, if not passed a "link" must be provided. */ id?: Id; /** Optional title. If not provided and a "link" is provided the title will be the Deep link title */ @@ -143,31 +222,8 @@ export interface NodeDefinition< link?: LinkId; /** Cloud link id */ cloudLink?: CloudLinkId; - /** Optional icon for the navigation node. Note: not all navigation depth will render the icon */ - icon?: string; - /** - * Optional flag to indicate if the node must be treated as a group title. - * Can not be used with `children` - */ - isGroupTitle?: boolean; /** Optional children of the navigation node. Can not be used with `isGroupTitle` */ - children?: NonEmptyArray>; - /** - * Use href for absolute links only. Internal links should use "link". - */ - href?: string; - /** - * Optional function to get the active state. This function is called whenever the location changes. - */ - getIsActive?: GetIsActiveFn; - - /** - * Optional flag to indicate if the breadcrumb should be hidden when this node is active. - * @default 'visible' - */ - breadcrumbStatus?: 'hidden' | 'visible'; - - accordionProps?: Partial; + children?: Array>; } /** diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts index c6d56e6011fe9..0659fe0461664 100644 --- a/packages/shared-ux/chrome/navigation/index.ts +++ b/packages/shared-ux/chrome/navigation/index.ts @@ -12,11 +12,15 @@ export { DefaultNavigation, getPresets, Navigation } from './src/ui'; export type { GroupDefinition, + PresetDefinition, + ItemDefinition, NavigationGroupPreset, NavigationTreeDefinition, ProjectNavigationDefinition, RecentlyAccessedDefinition, RootNavigationItemDefinition, + PanelComponentProps, + PanelContent, } from './src/ui'; export type { NavigationServices } from './types'; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index d2e7f9b77ca22..e1761de6be6fd 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -29,6 +29,7 @@ export const getServicesMock = ({ navigateToUrl, onProjectNavigationChange: jest.fn(), activeNodes$: of(activeNodes), + isSideNavCollapsed: false, cloudLinks: { billingAndSub: { title: 'Mock Billing & Subscriptions', diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index d8ed78c48e7a9..df1416ec0f793 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -44,6 +44,7 @@ export class StorybookMock extends AbstractStorybookMock<{}, NavigationServices> navLinks$: params.navLinks$ ?? new BehaviorSubject([]), onProjectNavigationChange: params.onProjectNavigationChange ?? (() => undefined), activeNodes$: params.activeNodes$ ?? new BehaviorSubject([]), + isSideNavCollapsed: true, cloudLinks: { billingAndSub: { title: 'Billing & Subscriptions', diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index ebe7846b33378..fd3f774bd72fb 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -7,6 +7,7 @@ */ import React, { FC, useContext, useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; import { NavigationKibanaDependencies, NavigationServices } from '../types'; import { CloudLinks, getCloudLinks } from './cloud_links'; @@ -32,6 +33,7 @@ export const NavigationKibanaProvider: FC = ({ const { navigateToUrl } = core.application; const cloudLinks: CloudLinks = useMemo(() => (cloud ? getCloudLinks(cloud) : {}), [cloud]); + const isSideNavCollapsed = useObservable(chrome.getIsSideNavCollapsed$(), true); const value: NavigationServices = { basePath, @@ -42,6 +44,7 @@ export const NavigationKibanaProvider: FC = ({ onProjectNavigationChange: serverless.setNavigation, activeNodes$: serverless.getActiveNavigationNodes$(), cloudLinks, + isSideNavCollapsed, }; return {children}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap b/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap index 46ffd04af56d8..734d4e459a897 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap +++ b/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap @@ -10,10 +10,12 @@ Array [ "href": undefined, "id": "item1", "isActive": false, + "isGroup": false, "path": Array [ "group1", "item1", ], + "sideNavStatus": "visible", "title": "Item 1", }, Object { @@ -28,10 +30,12 @@ Array [ "href": undefined, "id": "item2", "isActive": false, + "isGroup": false, "path": Array [ "group1", "item2", ], + "sideNavStatus": "visible", "title": "Title from deeplink!", }, Object { @@ -46,10 +50,12 @@ Array [ "href": undefined, "id": "item3", "isActive": false, + "isGroup": false, "path": Array [ "group1", "item3", ], + "sideNavStatus": "visible", "title": "Deeplink title overriden", }, ], @@ -57,9 +63,11 @@ Array [ "href": undefined, "id": "group1", "isActive": false, + "isGroup": true, "path": Array [ "group1", ], + "sideNavStatus": "visible", "title": "Group 1", "type": "navGroup", }, @@ -77,10 +85,12 @@ Array [ "href": undefined, "id": "discover", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:analytics", "discover", ], + "sideNavStatus": "visible", "title": "Deeplink discover", }, Object { @@ -95,10 +105,12 @@ Array [ "href": undefined, "id": "dashboards", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:analytics", "dashboards", ], + "sideNavStatus": "visible", "title": "Deeplink dashboards", }, Object { @@ -113,10 +125,12 @@ Array [ "href": undefined, "id": "visualize", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:analytics", "visualize", ], + "sideNavStatus": "visible", "title": "Deeplink visualize", }, ], @@ -125,9 +139,11 @@ Array [ "icon": "stats", "id": "rootNav:analytics", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:analytics", ], + "sideNavStatus": "visible", "title": "Data exploration", "type": "navGroup", }, @@ -145,10 +161,12 @@ Array [ "href": undefined, "id": "ml:overview", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "ml:overview", ], + "sideNavStatus": "visible", "title": "Deeplink ml:overview", }, Object { @@ -163,10 +181,12 @@ Array [ "href": undefined, "id": "ml:notifications", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "ml:notifications", ], + "sideNavStatus": "visible", "title": "Deeplink ml:notifications", }, Object { @@ -183,11 +203,13 @@ Array [ "href": undefined, "id": "ml:anomalyDetection", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "anomaly_detection", "ml:anomalyDetection", ], + "sideNavStatus": "visible", "title": "Jobs", }, Object { @@ -202,11 +224,13 @@ Array [ "href": undefined, "id": "ml:anomalyExplorer", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "anomaly_detection", "ml:anomalyExplorer", ], + "sideNavStatus": "visible", "title": "Deeplink ml:anomalyExplorer", }, Object { @@ -221,11 +245,13 @@ Array [ "href": undefined, "id": "ml:singleMetricViewer", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "anomaly_detection", "ml:singleMetricViewer", ], + "sideNavStatus": "visible", "title": "Deeplink ml:singleMetricViewer", }, Object { @@ -240,11 +266,13 @@ Array [ "href": undefined, "id": "ml:settings", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "anomaly_detection", "ml:settings", ], + "sideNavStatus": "visible", "title": "Deeplink ml:settings", }, ], @@ -252,10 +280,12 @@ Array [ "href": undefined, "id": "anomaly_detection", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:ml", "anomaly_detection", ], + "sideNavStatus": "visible", "title": "Anomaly Detection", }, Object { @@ -272,11 +302,13 @@ Array [ "href": undefined, "id": "ml:dataFrameAnalytics", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "data_frame_analytics", "ml:dataFrameAnalytics", ], + "sideNavStatus": "visible", "title": "Jobs", }, Object { @@ -291,11 +323,13 @@ Array [ "href": undefined, "id": "ml:resultExplorer", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "data_frame_analytics", "ml:resultExplorer", ], + "sideNavStatus": "visible", "title": "Deeplink ml:resultExplorer", }, Object { @@ -310,11 +344,13 @@ Array [ "href": undefined, "id": "ml:analyticsMap", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "data_frame_analytics", "ml:analyticsMap", ], + "sideNavStatus": "visible", "title": "Deeplink ml:analyticsMap", }, ], @@ -322,10 +358,12 @@ Array [ "href": undefined, "id": "data_frame_analytics", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:ml", "data_frame_analytics", ], + "sideNavStatus": "visible", "title": "Data Frame Analytics", }, Object { @@ -342,11 +380,13 @@ Array [ "href": undefined, "id": "ml:nodesOverview", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "model_management", "ml:nodesOverview", ], + "sideNavStatus": "visible", "title": "Deeplink ml:nodesOverview", }, Object { @@ -361,11 +401,13 @@ Array [ "href": undefined, "id": "ml:nodes", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "model_management", "ml:nodes", ], + "sideNavStatus": "visible", "title": "Deeplink ml:nodes", }, ], @@ -373,10 +415,12 @@ Array [ "href": undefined, "id": "model_management", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:ml", "model_management", ], + "sideNavStatus": "visible", "title": "Model Management", }, Object { @@ -393,11 +437,13 @@ Array [ "href": undefined, "id": "ml:fileUpload", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "data_visualizer", "ml:fileUpload", ], + "sideNavStatus": "visible", "title": "File", }, Object { @@ -412,11 +458,13 @@ Array [ "href": undefined, "id": "ml:indexDataVisualizer", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "data_visualizer", "ml:indexDataVisualizer", ], + "sideNavStatus": "visible", "title": "Data view", }, Object { @@ -431,11 +479,13 @@ Array [ "href": undefined, "id": "ml:dataDrift", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "data_visualizer", "ml:dataDrift", ], + "sideNavStatus": "visible", "title": "Data drift", }, ], @@ -443,10 +493,12 @@ Array [ "href": undefined, "id": "data_visualizer", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:ml", "data_visualizer", ], + "sideNavStatus": "visible", "title": "Data Visualizer", }, Object { @@ -463,11 +515,13 @@ Array [ "href": undefined, "id": "ml:logRateAnalysis", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "aiops_labs", "ml:logRateAnalysis", ], + "sideNavStatus": "visible", "title": "Deeplink ml:logRateAnalysis", }, Object { @@ -482,11 +536,13 @@ Array [ "href": undefined, "id": "ml:logPatternAnalysis", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "aiops_labs", "ml:logPatternAnalysis", ], + "sideNavStatus": "visible", "title": "Deeplink ml:logPatternAnalysis", }, Object { @@ -501,11 +557,13 @@ Array [ "href": undefined, "id": "ml:changePointDetections", "isActive": false, + "isGroup": false, "path": Array [ "rootNav:ml", "aiops_labs", "ml:changePointDetections", ], + "sideNavStatus": "visible", "title": "Deeplink ml:changePointDetections", }, ], @@ -513,10 +571,12 @@ Array [ "href": undefined, "id": "aiops_labs", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:ml", "aiops_labs", ], + "sideNavStatus": "visible", "title": "AIOps labs", }, ], @@ -525,9 +585,11 @@ Array [ "icon": "machineLearningApp", "id": "rootNav:ml", "isActive": false, + "isGroup": true, "path": Array [ "rootNav:ml", ], + "sideNavStatus": "visible", "title": "Machine Learning", "type": "navGroup", }, @@ -544,11 +606,13 @@ Array [ "icon": "editorCodeBlock", "id": "devTools", "isActive": false, + "isGroup": false, "path": Array [ "devTools", ], + "sideNavStatus": "visible", "title": "Developer tools", - "type": "navGroup", + "type": "navItem", }, Object { "breadcrumbStatus": "hidden", @@ -565,10 +629,12 @@ Array [ "href": undefined, "id": "management", "isActive": false, + "isGroup": false, "path": Array [ "project_settings_project_nav", "management", ], + "sideNavStatus": "visible", "title": "Management", }, Object { @@ -577,10 +643,12 @@ Array [ "href": "https://cloud.elastic.co/deployments/123456789/security/users", "id": "cloudLinkUserAndRoles", "isActive": false, + "isGroup": false, "path": Array [ "project_settings_project_nav", "cloudLinkUserAndRoles", ], + "sideNavStatus": "visible", "title": "Mock Users & Roles", }, Object { @@ -589,10 +657,12 @@ Array [ "href": "https://cloud.elastic.co/account/billing", "id": "cloudLinkBilling", "isActive": false, + "isGroup": false, "path": Array [ "project_settings_project_nav", "cloudLinkBilling", ], + "sideNavStatus": "visible", "title": "Mock Billing & Subscriptions", }, ], @@ -601,9 +671,11 @@ Array [ "icon": "gear", "id": "project_settings_project_nav", "isActive": false, + "isGroup": true, "path": Array [ "project_settings_project_nav", ], + "sideNavStatus": "visible", "title": "Project settings", "type": "navGroup", }, diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/index.ts b/packages/shared-ux/chrome/navigation/src/ui/components/index.ts index 2174b35603525..29de9b8d21dad 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/index.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/components/index.ts @@ -8,3 +8,4 @@ export { Navigation } from './navigation'; export type { Props as RecentlyAccessedProps } from './recently_accessed'; +export type { PanelContent, PanelComponentProps } from './panel'; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx index a6a40347068e3..bcf8650373b80 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx @@ -74,60 +74,114 @@ describe('', () => { onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1]; const [navTree] = lastCall; - expect(navTree.navigationTree).toEqual([ - { - children: [ - { - href: 'https://foo', - id: 'item1', - isActive: false, - path: ['group1', 'item1'], - title: 'Item 1', - }, - { - href: 'https://foo', - id: 'item2', - isActive: false, - path: ['group1', 'item2'], - title: 'Item 2', - }, - { - children: [ - { - href: 'https://foo', - id: 'item1', - isActive: false, - path: ['group1', 'group1A', 'item1'], - title: 'Group 1A Item 1', - }, - { - children: [ - { - href: 'https://foo', - id: 'item1', - isActive: false, - path: ['group1', 'group1A', 'group1A_1', 'item1'], - title: 'Group 1A_1 Item 1', - }, - ], - id: 'group1A_1', - isActive: true, - path: ['group1', 'group1A', 'group1A_1'], - title: 'Group1A_1', - }, - ], - id: 'group1A', - isActive: true, - path: ['group1', 'group1A'], - title: 'Group1A', - }, - ], - id: 'group1', - isActive: true, - path: ['group1'], - title: '', - }, - ]); + expect(navTree.navigationTree).toMatchInlineSnapshot(` + Array [ + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": undefined, + "href": "https://foo", + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "group1", + "item1", + ], + "sideNavStatus": "visible", + "title": "Item 1", + }, + Object { + "children": undefined, + "deepLink": undefined, + "href": "https://foo", + "id": "item2", + "isActive": false, + "isGroup": false, + "path": Array [ + "group1", + "item2", + ], + "sideNavStatus": "visible", + "title": "Item 2", + }, + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": undefined, + "href": "https://foo", + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "group1", + "group1A", + "item1", + ], + "sideNavStatus": "visible", + "title": "Group 1A Item 1", + }, + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": undefined, + "href": "https://foo", + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "group1", + "group1A", + "group1A_1", + "item1", + ], + "sideNavStatus": "visible", + "title": "Group 1A_1 Item 1", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "group1A_1", + "isActive": true, + "isGroup": true, + "path": Array [ + "group1", + "group1A", + "group1A_1", + ], + "sideNavStatus": "visible", + "title": "Group1A_1", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "group1A", + "isActive": true, + "isGroup": true, + "path": Array [ + "group1", + "group1A", + ], + "sideNavStatus": "visible", + "title": "Group1A", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "group1", + "isActive": true, + "isGroup": true, + "path": Array [ + "group1", + ], + "sideNavStatus": "visible", + "title": "", + }, + ] + `); }); test('should read the title from props, children or deeplink', async () => { @@ -172,62 +226,111 @@ describe('', () => { onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1]; const [navTree] = lastCall; - expect(navTree.navigationTree).toEqual([ - { - id: 'root', - path: ['root'], - title: '', - isActive: false, - children: [ - { - id: 'group1', - path: ['root', 'group1'], - title: '', - isActive: false, - children: [ - { - id: 'item1', - path: ['root', 'group1', 'item1'], - title: 'Title from deeplink', - isActive: false, - deepLink: { - id: 'item1', - title: 'Title from deeplink', - baseUrl: '', - url: '', - href: '', + expect(navTree.navigationTree).toMatchInlineSnapshot(` + Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": Object { + "baseUrl": "", + "href": "", + "id": "item1", + "title": "Title from deeplink", + "url": "", + }, + "href": undefined, + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "root", + "group1", + "item1", + ], + "sideNavStatus": "visible", + "title": "Title from deeplink", }, - }, - { - id: 'item2', - title: 'Overwrite deeplink title', - path: ['root', 'group1', 'item2'], - isActive: false, - deepLink: { - id: 'item1', - title: 'Title from deeplink', - baseUrl: '', - url: '', - href: '', + Object { + "children": undefined, + "deepLink": Object { + "baseUrl": "", + "href": "", + "id": "item1", + "title": "Title from deeplink", + "url": "", + }, + "href": undefined, + "id": "item2", + "isActive": false, + "isGroup": false, + "path": Array [ + "root", + "group1", + "item2", + ], + "sideNavStatus": "visible", + "title": "Overwrite deeplink title", }, - }, - { - id: 'item3', - title: 'Title in props', - isActive: false, - path: ['root', 'group1', 'item3'], - }, - { - id: 'item4', - path: ['root', 'group1', 'item4'], - title: 'Title in children', - isActive: false, - }, - ], - }, - ], - }, - ]); + Object { + "children": undefined, + "deepLink": undefined, + "href": undefined, + "id": "item3", + "isActive": false, + "isGroup": false, + "path": Array [ + "root", + "group1", + "item3", + ], + "sideNavStatus": "visible", + "title": "Title in props", + }, + Object { + "children": undefined, + "deepLink": undefined, + "href": undefined, + "id": "item4", + "isActive": false, + "isGroup": false, + "path": Array [ + "root", + "group1", + "item4", + ], + "sideNavStatus": "visible", + "title": "Title in children", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "group1", + "isActive": false, + "isGroup": true, + "path": Array [ + "root", + "group1", + ], + "sideNavStatus": "visible", + "title": "", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "root", + "isActive": false, + "isGroup": true, + "path": Array [ + "root", + ], + "sideNavStatus": "visible", + "title": "", + }, + ] + `); }); test('should filter out unknown deeplinks', async () => { @@ -274,37 +377,60 @@ describe('', () => { onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1]; const [navTree] = lastCall; - expect(navTree.navigationTree).toEqual([ - { - id: 'root', - path: ['root'], - title: '', - isActive: true, - children: [ - { - id: 'group1', - path: ['root', 'group1'], - title: '', - isActive: true, - children: [ - { - id: 'item1', - path: ['root', 'group1', 'item1'], - title: 'Title from deeplink', - isActive: false, - deepLink: { - id: 'item1', - title: 'Title from deeplink', - baseUrl: '', - url: '', - href: '', + expect(navTree.navigationTree).toMatchInlineSnapshot(` + Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": Object { + "baseUrl": "", + "href": "", + "id": "item1", + "title": "Title from deeplink", + "url": "", + }, + "href": undefined, + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "root", + "group1", + "item1", + ], + "sideNavStatus": "visible", + "title": "Title from deeplink", }, - }, - ], - }, - ], - }, - ]); + ], + "deepLink": undefined, + "href": undefined, + "id": "group1", + "isActive": true, + "isGroup": true, + "path": Array [ + "root", + "group1", + ], + "sideNavStatus": "visible", + "title": "", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "root", + "isActive": true, + "isGroup": true, + "path": Array [ + "root", + ], + "sideNavStatus": "visible", + "title": "", + }, + ] + `); }); test('should not render the group if it does not have children AND no href or deeplink', async () => { @@ -350,43 +476,74 @@ describe('', () => { onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1]; const [navTree] = lastCall; - expect(navTree.navigationTree).toEqual([ - { - children: [ - { - id: 'group1', - isActive: true, - path: ['root', 'group1'], - title: '', - }, - { - children: [ - { - deepLink: { - baseUrl: '', - href: '', - id: 'item1', - title: 'Title from deeplink', - url: '', + expect(navTree.navigationTree).toMatchInlineSnapshot(` + Array [ + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": undefined, + "href": undefined, + "id": "group1", + "isActive": true, + "isGroup": true, + "path": Array [ + "root", + "group1", + ], + "sideNavStatus": "visible", + "title": "", + }, + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": Object { + "baseUrl": "", + "href": "", + "id": "item1", + "title": "Title from deeplink", + "url": "", + }, + "href": undefined, + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "root", + "group2", + "item1", + ], + "sideNavStatus": "visible", + "title": "Title from deeplink", }, - id: 'item1', - isActive: false, - path: ['root', 'group2', 'item1'], - title: 'Title from deeplink', - }, - ], - id: 'group2', - isActive: true, - path: ['root', 'group2'], - title: '', - }, - ], - id: 'root', - isActive: true, - path: ['root'], - title: '', - }, - ]); + ], + "deepLink": undefined, + "href": undefined, + "id": "group2", + "isActive": true, + "isGroup": true, + "path": Array [ + "root", + "group2", + ], + "sideNavStatus": "visible", + "title": "", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "root", + "isActive": true, + "isGroup": true, + "path": Array [ + "root", + ], + "sideNavStatus": "visible", + "title": "", + }, + ] + `); }); test('should render group preset (analytics, ml...)', async () => { @@ -467,23 +624,38 @@ describe('', () => { onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1]; const [navTreeGenerated] = lastCall; - expect(navTreeGenerated.navigationTree).toEqual([ - { - id: 'group1', - path: ['group1'], - title: '', - isActive: false, - children: [ - { - id: 'item1', - title: 'Item 1', - isActive: false, - href: 'https://example.com', - path: ['group1', 'item1'], - }, - ], - }, - ]); + expect(navTreeGenerated.navigationTree).toMatchInlineSnapshot(` + Array [ + Object { + "children": Array [ + Object { + "children": undefined, + "deepLink": undefined, + "href": "https://example.com", + "id": "item1", + "isActive": false, + "isGroup": false, + "path": Array [ + "group1", + "item1", + ], + "sideNavStatus": "visible", + "title": "Item 1", + }, + ], + "deepLink": undefined, + "href": undefined, + "id": "group1", + "isActive": false, + "isGroup": true, + "path": Array [ + "group1", + ], + "sideNavStatus": "visible", + "title": "", + }, + ] + `); }); test('should throw if href is not an absolute links', async () => { diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx index 4530a0c8b6b09..83dc748047a93 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx @@ -27,6 +27,7 @@ import { NavigationGroup } from './navigation_group'; import { NavigationItem } from './navigation_item'; import { NavigationUI } from './navigation_ui'; import { RecentlyAccessed } from './recently_accessed'; +import { PanelProvider, type ContentProvider } from './panel'; interface Context { register: RegisterFunction; @@ -47,6 +48,12 @@ const NavigationContext = createContext({ interface Props { children: ReactNode; + /** + * Optional content provider for the navigation panels. Use it to render custom component + * inside the panel when the user clicks on a navigation item. + * If not provided the default content will be rendered. + */ + panelContentProvider?: ContentProvider; /** * Flag to indicate if the Navigation should not be styled with EUI components. * If set to true, the children will be rendered as is. @@ -55,7 +62,12 @@ interface Props { dataTestSubj?: string; } -export function Navigation({ children, unstyled = false, dataTestSubj }: Props) { +export function Navigation({ + children, + panelContentProvider, + unstyled = false, + dataTestSubj, +}: Props) { const { onProjectNavigationChange, activeNodes$ } = useNavigationServices(); // We keep a reference of the order of the children that register themselves when mounting. @@ -134,11 +146,17 @@ export function Navigation({ children, unstyled = false, dataTestSubj }: Props) }, [debouncedNavigationItems, onProjectNavigationChange]); return ( - - - {children} - - + + + + {children} + + + ); } diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx index f28bf7ce6151a..907d155cbd061 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx @@ -7,8 +7,9 @@ */ import React, { useCallback } from 'react'; - import type { AppDeepLinkId, NodeDefinition } from '@kbn/core-chrome-browser'; + +import { getNavigationNodeId } from '../../utils'; import { getPresets } from '../nav_tree_presets'; import { Navigation } from './navigation'; import type { NavigationGroupPreset } from '../types'; @@ -41,13 +42,7 @@ export function NavigationBucket< const renderItems = useCallback( (items: Array>, isRoot = false) => { return items.map((item) => { - const id = item.id ?? item.link; - - if (!id) { - throw new Error( - `At least one of id or link must be defined for navigation item ${item.title}` - ); - } + const id = getNavigationNodeId(item); return ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_group.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_group.tsx index 070f156943b27..93e451aa7f16f 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_group.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_group.tsx @@ -7,11 +7,11 @@ */ import React, { createContext, useCallback, useMemo, useContext } from 'react'; -import type { AppDeepLinkId } from '@kbn/core-chrome-browser'; +import type { AppDeepLinkId, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; import { useNavigation as useNavigationServices } from '../../services'; import { useInitNavNode } from '../hooks'; -import type { NodeProps, RegisterFunction } from '../types'; +import type { NodeProps, NodePropsEnhanced, RegisterFunction } from '../types'; import { NavigationSectionUI } from './navigation_section_ui'; import { useNavigation } from './navigation'; import { NavigationBucket, type Props as NavigationBucketProps } from './navigation_bucket'; @@ -51,21 +51,37 @@ function NavigationGroupInternalComp< const { children, node } = useMemo(() => { const { children: _children, defaultIsCollapsed, ...rest } = props; + const nodeEnhanced: Omit, 'children'> = { + ...rest, + isActive: defaultIsCollapsed !== undefined ? defaultIsCollapsed === false : undefined, + isGroup: true, + }; return { children: _children, - node: { - ...rest, - isActive: defaultIsCollapsed !== undefined ? defaultIsCollapsed === false : undefined, - }, + node: nodeEnhanced, }; }, [props]); const { navNode, registerChildNode, path, childrenNodes } = useInitNavNode(node, { cloudLinks }); + // We add to the nav node the children that have mounted and registered themselves. + // Those children render in the UI inside the NavigationSectionUI -> EuiCollapsibleNavItem -> items + const navNodeWithChildren = useMemo(() => { + if (!navNode) return null; + + const hasChildren = Object.keys(childrenNodes).length > 0; + const withChildren: ChromeProjectNavigationNode = { + ...navNode, + children: hasChildren ? Object.values(childrenNodes) : undefined, + }; + + return withChildren; + }, [navNode, childrenNodes]); + const unstyled = props.unstyled ?? navigationContext.unstyled; const renderContent = useCallback(() => { - if (!path || !navNode) { + if (!path || !navNodeWithChildren) { return null; } @@ -74,22 +90,20 @@ function NavigationGroupInternalComp< return children; } - // Each "top level" group is rendered using the EuiCollapsibleNavGroup component - // inside the NavigationSectionUI. That's how we get the "collapsible" behavior. - const isTopLevel = path && path.length === 1; + // We will only render the component for root groups. The nested group + // are handled by the EuiCollapsibleNavItem component through its "items" prop. + const isRootLevel = path && path.length === 1; return ( <> - {isTopLevel && ( - - )} - {/* We render the children so they mount and can register themselves but - visually they don't appear here in the DOM. They are rendered inside the - "items" prop (see ) */} + {isRootLevel && } + {/* We render the children so they mount and can **register** themselves but + visually they don't appear here in the DOM. They are rendered inside the + "items" prop (see ) */} {children} ); - }, [navNode, path, childrenNodes, children, unstyled]); + }, [navNodeWithChildren, path, children, unstyled]); const contextValue = useMemo(() => { return { diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx index a48e0b771ece5..8c18f350bb0bf 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx @@ -7,12 +7,14 @@ */ import React, { Fragment, useEffect, useMemo } from 'react'; - import type { AppDeepLinkId, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import { EuiCollapsibleNavItem } from '@elastic/eui'; + import { useNavigation as useNavigationServices } from '../../services'; import { useInitNavNode } from '../hooks'; -import type { NodeProps } from '../types'; +import type { NodeProps, NodePropsEnhanced } from '../types'; import { useNavigation } from './navigation'; +import { getNavigationNodeHref } from '../../utils'; export interface Props< LinkId extends AppDeepLinkId = AppDeepLinkId, @@ -27,37 +29,75 @@ function NavigationItemComp< Id extends string = string, ChildrenId extends string = Id >(props: Props) { - const { cloudLinks } = useNavigationServices(); + const { cloudLinks, navigateToUrl } = useNavigationServices(); const navigationContext = useNavigation(); const navNodeRef = React.useRef(null); const { children, node } = useMemo(() => { const { children: _children, ...rest } = props; + const nodeEnhanced: Omit, 'children'> = { + ...rest, + isGroup: false, + }; + if (typeof _children === 'string') { + nodeEnhanced.title = nodeEnhanced.title ?? _children; + } return { children: _children, - node: rest, + node: nodeEnhanced, }; }, [props]); const unstyled = props.unstyled ?? navigationContext.unstyled; - const { navNode } = useInitNavNode({ ...node, children }, { cloudLinks }); + const { navNode } = useInitNavNode(node, { cloudLinks }); useEffect(() => { navNodeRef.current = navNode; }, [navNode]); - if (!navNode || !unstyled) { + if (!navNode) { return null; } - if (children) { - if (typeof children === 'function') { - return children(navNode); + if (unstyled) { + if (children) { + if (typeof children === 'function') { + return children(navNode); + } + return <>{children}; } - return <>{children}; + + return {navNode.title}; + } + + const isRootLevel = navNode.path.length === 1; + + if (isRootLevel) { + const href = getNavigationNodeHref(navNode); + return ( + { + e.preventDefault(); + e.stopPropagation(); + if (href) { + navigateToUrl(href); + } + }, + }} + /> + ); } - return {navNode.title}; + // We don't render anything in the UI for non root item as those register themselves on the parent (Group) + // updating its "childrenNodes" state which are then converted to "items" for the EuiCollapsibleNavItem component. + return null; } export const NavigationItem = React.memo(NavigationItemComp) as typeof NavigationItemComp; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx new file mode 100644 index 0000000000000..d38c3aa336be7 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx @@ -0,0 +1,113 @@ +/* + * 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 React, { useCallback, type FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import classNames from 'classnames'; +import { css } from '@emotion/css'; +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiListGroup, + EuiListGroupItem, + type EuiThemeComputed, + useEuiTheme, + transparentize, +} from '@elastic/eui'; +import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import type { NavigateToUrlFn } from '../../../types/internal'; +import { usePanel } from './panel'; +import { nodePathToString } from '../../utils'; + +const getStyles = (euiTheme: EuiThemeComputed<{}>) => css` + * { + // EuiListGroupItem changes the links font-weight, we need to override it + font-weight: ${euiTheme.font.weight.regular}; + } + &.sideNavItem:hover { + background-color: transparent; + } + &.sideNavItem--isActive:hover, + &.sideNavItem--isActive { + background-color: ${transparentize(euiTheme.colors.lightShade, 0.5)}; + & * { + font-weight: ${euiTheme.font.weight.medium}; + } + } +`; + +interface Props { + item: ChromeProjectNavigationNode; + navigateToUrl: NavigateToUrlFn; +} + +export const NavigationItemOpenPanel: FC = ({ item, navigateToUrl }: Props) => { + const { euiTheme } = useEuiTheme(); + const { open: openPanel, close: closePanel, selectedNode } = usePanel(); + const { title, deepLink, isActive, children } = item; + const id = nodePathToString(item); + const href = deepLink?.url ?? item.href; + + const itemClassNames = classNames( + 'sideNavItem', + { 'sideNavItem--isActive': isActive }, + getStyles(euiTheme) + ); + + const onLinkClick = useCallback( + (e: React.MouseEvent) => { + if (!href) { + return; + } + e.preventDefault(); + navigateToUrl(href); + closePanel(); + }, + [closePanel, href, navigateToUrl] + ); + + const onIconClick = useCallback(() => { + openPanel(item); + }, [openPanel, item]); + + return ( + + + + + + + {!!children && children.length > 0 && ( + + + + )} + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx index 2d31154ddf2e3..11f7dfc898f90 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx @@ -6,38 +6,117 @@ * Side Public License, v 1. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC } from 'react'; import { + EuiAccordionProps, EuiCollapsibleNavItem, EuiCollapsibleNavItemProps, EuiCollapsibleNavSubItemProps, EuiTitle, } from '@elastic/eui'; -import { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; import classnames from 'classnames'; -import type { BasePathService, NavigateToUrlFn } from '../../../types/internal'; + +import type { NavigateToUrlFn } from '../../../types/internal'; import { useNavigation as useServices } from '../../services'; -import { isAbsoluteLink } from '../../utils'; - -const navigationNodeToEuiItem = ( - item: ChromeProjectNavigationNode, - { navigateToUrl, basePath }: { navigateToUrl: NavigateToUrlFn; basePath: BasePathService } -): EuiCollapsibleNavSubItemProps => { - const href = item.deepLink?.url ?? item.href; - const id = item.path ? item.path.join('.') : item.id; +import { nodePathToString, isAbsoluteLink, getNavigationNodeHref } from '../../utils'; +import { PanelContext, usePanel } from './panel'; +import { NavigationItemOpenPanel } from './navigation_item_open_panel'; + +const nodeHasLink = (navNode: ChromeProjectNavigationNode) => + Boolean(navNode.deepLink) || Boolean(navNode.href); + +const nodeHasChildren = (navNode: ChromeProjectNavigationNode) => Boolean(navNode.children?.length); + +/** + * Predicate to determine if a node should be visible in the main side nav. + * If it is not visible it will be filtered out and not rendered. + */ +const itemIsVisible = (item: ChromeProjectNavigationNode) => { + if (item.sideNavStatus === 'hidden') return false; + + const isGroupTitle = Boolean(item.isGroupTitle); + if (isGroupTitle) { + return true; + } + + if (nodeHasLink(item)) { + return true; + } + + if (nodeHasChildren(item)) { + return item.renderAs === 'item' ? true : item.children!.some(itemIsVisible); + } + + return false; +}; + +const filterChildren = ( + children?: ChromeProjectNavigationNode[] +): ChromeProjectNavigationNode[] | undefined => { + if (!children) return undefined; + return children.filter(itemIsVisible); +}; + +const serializeNavNode = (navNode: ChromeProjectNavigationNode) => { + const serialized = { + ...navNode, + id: nodePathToString(navNode), + children: filterChildren(navNode.children), + href: getNavigationNodeHref(navNode), + }; + + return { + navNode: serialized, + hasChildren: nodeHasChildren(serialized), + hasLink: nodeHasLink(serialized), + isItem: serialized.renderAs === 'item' || serialized.children === undefined, + }; +}; + +const isEuiCollapsibleNavItemProps = ( + props: EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps +): props is EuiCollapsibleNavItemProps => { + return ( + props.title !== undefined && (props as EuiCollapsibleNavSubItemProps).renderItem === undefined + ); +}; + +// Generate the EuiCollapsible props for both the root component (EuiCollapsibleNavItem) and its +// "items" props. Both are compatible with the exception of "renderItem" which is only used for +// sub items. +const nodeToEuiCollapsibleNavProps = ( + _navNode: ChromeProjectNavigationNode, + { + navigateToUrl, + openPanel, + closePanel, + isSideNavCollapsed, + treeDepth, + }: { + navigateToUrl: NavigateToUrlFn; + openPanel: PanelContext['open']; + closePanel: PanelContext['close']; + isSideNavCollapsed: boolean; + treeDepth: number; + } +): { props: EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps; isVisible: boolean } => { + const { navNode, isItem, hasChildren, hasLink } = serializeNavNode(_navNode); + + const { id, title, href, icon, renderAs, isActive, deepLink, isGroupTitle } = navNode; const isExternal = Boolean(href) && isAbsoluteLink(href!); - const isSelected = item.children && item.children.length > 0 ? false : item.isActive; + const isSelected = hasChildren ? false : isActive; const dataTestSubj = classnames(`nav-item`, `nav-item-${id}`, { - [`nav-item-deepLinkId-${item.deepLink?.id}`]: !!item.deepLink, - [`nav-item-id-${item.id}`]: item.id, + [`nav-item-deepLinkId-${deepLink?.id}`]: !!deepLink, + [`nav-item-id-${id}`]: id, [`nav-item-isActive`]: isSelected, }); // Note: this can be replaced with an `isGroup` API or whatever you prefer // Could also probably be pulled out to a separate component vs inlined - if (item.isGroupTitle) { - return { + if (isGroupTitle) { + const props: EuiCollapsibleNavSubItemProps = { renderItem: () => (
- {item.title} + {title}
), }; + return { props, isVisible: true }; } - return { - id, - title: item.title, - isSelected, - accordionProps: { - ...item.accordionProps, - initialIsOpen: true, // FIXME open state is controlled on component mount - }, - linkProps: { external: isExternal }, - onClick: - href !== undefined - ? (event: React.MouseEvent) => { - event.preventDefault(); + if (renderAs === 'panelOpener') { + const props: EuiCollapsibleNavSubItemProps = { + renderItem: () => , + }; + return { props, isVisible: true }; + } + + const onClick = (e: React.MouseEvent) => { + if (href !== undefined) { + e.preventDefault(); + navigateToUrl(href); + closePanel(); + return; + } + }; + + const items: EuiCollapsibleNavItemProps['items'] = isItem + ? undefined + : navNode.children + ?.map((child) => + nodeToEuiCollapsibleNavProps(child, { + navigateToUrl, + openPanel, + closePanel, + isSideNavCollapsed, + treeDepth: treeDepth + 1, + }) + ) + .filter(({ isVisible }) => isVisible) + .map((res) => res.props); + + const linkProps: EuiCollapsibleNavItemProps['linkProps'] | undefined = hasLink + ? { + href, + external: isExternal, + onClick: (e: React.MouseEvent) => { + // TODO: here we might want to toggle the accordion (if we "renderAs: 'accordion'") + // Will be done in following PR + e.preventDefault(); + e.stopPropagation(); + if (href) { navigateToUrl(href); } - : undefined, + }, + } + : undefined; + + const accordionProps: Partial | undefined = isItem + ? undefined + : { + initialIsOpen: treeDepth === 0 ? isActive : true, // FIXME open state is controlled on component mount + ...navNode.accordionProps, + }; + + const props: EuiCollapsibleNavItemProps = { + id, + title, + isSelected, + accordionProps, + linkProps, + onClick, href, - items: item.children?.map((_item) => - navigationNodeToEuiItem(_item, { navigateToUrl, basePath }) - ), + items, ['data-test-subj']: dataTestSubj, - icon: item.icon, - iconProps: { size: 's' }, + icon, + iconProps: { size: treeDepth === 0 ? 'm' : 's' }, }; + + const hasVisibleChildren = (items?.length ?? 0) > 0; + return { props, isVisible: isItem || hasVisibleChildren }; }; interface Props { navNode: ChromeProjectNavigationNode; - items?: ChromeProjectNavigationNode[]; } -export const NavigationSectionUI: FC = ({ navNode, items = [] }) => { - const { id, title, icon, isActive } = navNode; - const { navigateToUrl, basePath } = useServices(); - const [isCollapsed, setIsCollapsed] = useState(!isActive); - // We want to auto expand the group automatically if the node is active (URL match) - // but once the user manually expand a group we don't want to close it afterward automatically. - const [doCollapseFromActiveState, setDoCollapseFromActiveState] = useState(true); - - // If the item has no link and no cildren, we don't want to render it - const itemHasLinkOrChildren = (item: ChromeProjectNavigationNode) => { - const isGroupTitle = Boolean(item.isGroupTitle); - const hasLink = Boolean(item.deepLink) || Boolean(item.href); - if (isGroupTitle) { - return true; - } - if (hasLink) { - return true; - } - const hasChildren = Boolean(item.children?.length); - if (hasChildren) { - return item.children!.some(itemHasLinkOrChildren); - } - return false; - }; +export const NavigationSectionUI: FC = ({ navNode }) => { + const { navigateToUrl, isSideNavCollapsed } = useServices(); + const { open: openPanel, close: closePanel } = usePanel(); - const filteredItems = items.filter(itemHasLinkOrChildren).map((item) => { - if (item.children) { - return { - ...item, - children: item.children.filter(itemHasLinkOrChildren), - }; - } - return item; + const { props, isVisible } = nodeToEuiCollapsibleNavProps(navNode, { + navigateToUrl, + openPanel, + closePanel, + isSideNavCollapsed, + treeDepth: 0, }); - const groupHasLink = Boolean(navNode.deepLink) || Boolean(navNode.href); - const groupHasChildren = filteredItems.some(itemHasLinkOrChildren); - // Group with a link and no children will be rendered as a link and not an EUI accordion - const groupIsLink = groupHasLink && !groupHasChildren; - const groupHref = navNode.deepLink?.url ?? navNode.href!; - - useEffect(() => { - if (doCollapseFromActiveState) { - setIsCollapsed(!isActive); - } - }, [isActive, doCollapseFromActiveState]); + if (!isEuiCollapsibleNavItemProps(props)) { + throw new Error(`Invalid EuiCollapsibleNavItem props for node ${props.id}`); + } - if (!groupHasLink && !groupHasChildren) { + if (!isVisible) { return null; } - const propsForGroupAsLink: Partial = groupIsLink - ? { - linkProps: { - href: groupHref, - onClick: (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - navigateToUrl(groupHref); - }, - }, - } - : {}; - - return ( - { - setIsCollapsed(!isOpen); - setDoCollapseFromActiveState(false); - }, - ...navNode.accordionProps, - }} - data-test-subj={`nav-bucket-${id}`} - {...propsForGroupAsLink} - items={filteredItems.map((item) => - navigationNodeToEuiItem(item, { navigateToUrl, basePath }) - )} - /> - ); + return ; }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx index 039823daa9a17..60424c1f4d03d 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx @@ -9,6 +9,8 @@ import { EuiCollapsibleNavBeta } from '@elastic/eui'; import React, { FC } from 'react'; +import { NavigationPanel } from './panel'; + interface Props { unstyled?: boolean; footerChildren?: React.ReactNode; @@ -22,12 +24,16 @@ export const NavigationUI: FC = ({ children, unstyled, footerChildren, da <>{children} ) : ( <> + {/* Main navigation content */} {children} {footerChildren && ( {footerChildren} )} + + {/* Right side panel navigation */} + )} diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/context.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/context.tsx new file mode 100644 index 0000000000000..f9720a38a6655 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/context.tsx @@ -0,0 +1,97 @@ +/* + * 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 React, { type FC, useCallback, useContext, useMemo, useState, ReactNode } from 'react'; +import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; + +import { nodePathToString } from '../../../utils'; +import { DefaultContent } from './default_content'; +import { ContentProvider, PanelNavNode } from './types'; + +export interface PanelContext { + isOpen: boolean; + toggle: () => void; + open: (navNode: PanelNavNode) => void; + close: () => void; + /** The selected node is the node in the main panel that opens the Panel */ + selectedNode: PanelNavNode | null; + /** Handler to retrieve the component to render in the panel */ + getContent: () => React.ReactNode; +} + +const Context = React.createContext(null); + +interface Props { + contentProvider?: ContentProvider; + activeNodes: ChromeProjectNavigationNode[][]; +} + +export const PanelProvider: FC = ({ children, contentProvider, activeNodes }) => { + const [isOpen, setIsOpen] = useState(false); + const [selectedNode, setActiveNode] = useState(null); + + const toggle = useCallback(() => { + setIsOpen((prev) => !prev); + }, []); + + const open = useCallback((navNode: PanelNavNode) => { + setActiveNode(navNode); + setIsOpen(true); + }, []); + + const close = useCallback(() => { + setActiveNode(null); + setIsOpen(false); + }, []); + + const getContent = useCallback(() => { + if (!selectedNode) { + return null; + } + + const provided = contentProvider?.(nodePathToString(selectedNode)); + + if (!provided) { + return ; + } + + if (provided.content) { + const Component = provided.content; + return ; + } + + const title: string | ReactNode = provided.title ?? selectedNode.title; + return ; + }, [selectedNode, contentProvider, close, activeNodes]); + + const ctx: PanelContext = useMemo( + () => ({ + isOpen, + toggle, + open, + close, + selectedNode, + getContent, + }), + [isOpen, toggle, open, close, selectedNode, getContent] + ); + + return {children}; +}; + +export function usePanel() { + const context = useContext(Context); + + if (!context) { + throw new Error( + 'Panel Context is missing. Ensure your component or React root is wrapped with PanelProvider.' + ); + } + + return context; +} diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/default_content.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/default_content.tsx new file mode 100644 index 0000000000000..fd1c56884f17a --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/default_content.tsx @@ -0,0 +1,120 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import React, { Fragment, type FC } from 'react'; + +import { isGroupNode, isItemNode } from '../../../utils'; +import { PanelGroup } from './panel_group'; +import { PanelNavItem } from './panel_nav_item'; +import type { PanelNavNode } from './types'; + +/** + * All the children of a panel must be wrapped into groups. This is because a group in DOM is represented by
    tags + * inside the which then renders the items in
  • insid the component. + * Having
  • without
      is okayish but semantically it is not correct. + * This function checks if we only have items and automatically wraps them into a group. If there is a mix + * of items and groups it throws an error. + * + * @param node The current active node + * @returns The children serialized + */ +function serializeChildren(node: PanelNavNode): ChromeProjectNavigationNode[] | undefined { + if (!node.children) return undefined; + + const allChildrenAreItems = node.children.every((_node) => { + if (isItemNode(_node)) return true; + return _node.renderAs === 'item'; + }); + + if (allChildrenAreItems) { + // Automatically wrap all the children into top level "root" group. + return [ + { + id: 'root', + title: '', + path: [...node.path, 'root'], + children: [...node.children], + }, + ]; + } + + const allChildrenAreGroups = node.children.every((_node) => { + if (_node.renderAs === 'item') return false; + return isGroupNode(_node); + }); + + if (!allChildrenAreGroups) { + throw new Error( + `[Chrome navigation] Error in node [${node.id}]. Children must either all be "groups" or all "items" but not a mix of both.` + ); + } + + return node.children; +} + +interface Props { + /** The selected node is the node in the main panel that opens the Panel */ + selectedNode: PanelNavNode; +} + +export const DefaultContent: FC = ({ selectedNode }) => { + const filteredChildren = selectedNode.children?.filter( + (child) => child.sideNavStatus !== 'hidden' + ); + const serializedChildren = serializeChildren({ ...selectedNode, children: filteredChildren }); + const totalChildren = serializedChildren?.length ?? 0; + const firstChildIsGroup = !!serializedChildren?.[0]?.children; + const firstGroupTitle = firstChildIsGroup && serializedChildren?.[0]?.title; + const firstGroupHasTitle = !!firstGroupTitle; + + return ( + + + {typeof selectedNode.title === 'string' ? ( + +

      {selectedNode.title}

      +
      + ) : ( + selectedNode.title + )} +
      + + + <> + {firstGroupHasTitle && } + + {serializedChildren && ( + <> + {serializedChildren.map((child, i) => { + const hasHorizontalRuleBefore = + i === 0 ? false : !!serializedChildren?.[i - 1]?.appendHorizontalRule; + const isGroup = !!child.children; + return isGroup ? ( + + + {i < totalChildren - 1 && ( + + )} + + ) : ( + + ); + })} + + )} + + +
      + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/index.ts b/packages/shared-ux/chrome/navigation/src/ui/components/panel/index.ts new file mode 100644 index 0000000000000..23885330ee5f8 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { NavigationPanel } from './navigation_panel'; + +export { PanelProvider, usePanel } from './context'; +export type { PanelContext } from './context'; +export type { ContentProvider, PanelContent, PanelComponentProps } from './types'; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/label_badge.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/label_badge.tsx new file mode 100644 index 0000000000000..488b9a377cfcd --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/label_badge.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import { EuiBetaBadge, useEuiTheme } from '@elastic/eui'; + +export const BETA_LABEL = i18n.translate('sharedUXPackages.chrome.sideNavigation.betaBadge.label', { + defaultMessage: 'Beta', +}); + +export const LabelBadge = ({ + text = BETA_LABEL, + className, +}: { + /** Optional text for the badge. @default 'Beta' */ + text?: string; + className?: string; +}) => { + const { euiTheme } = useEuiTheme(); + + return ( + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx new file mode 100644 index 0000000000000..8cefab48569d3 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx @@ -0,0 +1,69 @@ +/* + * 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 { + EuiFocusTrap, + EuiOutsideClickDetector, + EuiPanel, + EuiWindowEvent, + keys, + useEuiTheme, +} from '@elastic/eui'; +import React, { useCallback, type FC } from 'react'; +import classNames from 'classnames'; + +import { usePanel } from './context'; +import { getNavPanelStyles, getPanelWrapperStyles } from './styles'; + +export const NavigationPanel: FC = () => { + const { euiTheme } = useEuiTheme(); + const { isOpen, close, getContent } = usePanel(); + + // ESC key closes PanelNav + const onKeyDown = useCallback( + (ev: KeyboardEvent) => { + if (ev.key === keys.ESCAPE) { + close(); + } + }, + [close] + ); + + const onOutsideClick = useCallback(() => { + close(); + }, [close]); + + const panelWrapperClasses = getPanelWrapperStyles(); + const sideNavPanelStyles = getNavPanelStyles(euiTheme); + const panelClasses = classNames('sideNavPanel', 'eui-yScroll', sideNavPanelStyles); + + if (!isOpen) { + return null; + } + + return ( + <> + +
      + + + + {getContent()} + + + +
      + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_group.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_group.tsx new file mode 100644 index 0000000000000..711adf401e7b1 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_group.tsx @@ -0,0 +1,126 @@ +/* + * 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 React, { FC, Fragment, useCallback } from 'react'; +import { + EuiListGroup, + EuiTitle, + useEuiTheme, + euiFontSize, + type EuiThemeComputed, + type UseEuiTheme, + EuiSpacer, + EuiAccordion, + EuiHorizontalRule, +} from '@elastic/eui'; +import { css } from '@emotion/css'; + +import { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import { PanelNavItem } from './panel_nav_item'; + +const accordionButtonClassName = 'sideNavPanelAccordion__button'; + +const getClassnames = (euiTheme: EuiThemeComputed<{}>) => ({ + title: css` + text-transform: uppercase; + color: ${euiTheme.colors.darkShade}; + padding-left: ${euiTheme.size.s}; + padding-bottom: ${euiTheme.size.s}; + ${euiFontSize({ euiTheme } as UseEuiTheme<{}>, 'xxs')} + font-weight: ${euiTheme.font.weight.medium}; + `, + accordion: css` + margin-bottom: ${euiTheme.size.s}; + .${accordionButtonClassName} { + font-weight: ${euiTheme.font.weight.bold}; + ${euiFontSize({ euiTheme } as UseEuiTheme<{}>, 'xs')} + } + `, +}); + +interface Props { + navNode: ChromeProjectNavigationNode; + /** Flag to indicate if the group is the first in the list of groups when looping */ + isFirstInList?: boolean; + /** Flag to indicate if an horizontal rule preceeds this group */ + hasHorizontalRuleBefore?: boolean; +} + +export const PanelGroup: FC = ({ navNode, isFirstInList, hasHorizontalRuleBefore }) => { + const { euiTheme } = useEuiTheme(); + const { id, title, appendHorizontalRule } = navNode; + const filteredChildren = navNode.children?.filter((child) => child.sideNavStatus !== 'hidden'); + const totalChildren = filteredChildren?.length ?? 0; + const classNames = getClassnames(euiTheme); + const hasTitle = !!title && title !== ''; + const removePaddingTop = !hasTitle && !isFirstInList; + const someChildIsGroup = filteredChildren?.some((child) => !!child.children); + const firstChildIsGroup = !!filteredChildren?.[0]?.children; + + const renderChildren = useCallback(() => { + if (!filteredChildren) return null; + + return filteredChildren.map((item, i) => { + const isItem = item.renderAs === 'item' || !item.children; + return isItem ? ( + + ) : ( + + + {i < totalChildren - 1 && } + + ); + }); + }, [filteredChildren, totalChildren]); + + if (!filteredChildren?.length) { + return null; + } + + if (navNode.renderAs === 'accordion') { + return ( + <> + + <> + + {renderChildren()} + + + {appendHorizontalRule && } + + ); + } + + return ( + <> + {hasTitle && ( + +

      {navNode.title}

      +
      + )} + + {renderChildren()} + + {appendHorizontalRule && } + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item.tsx new file mode 100644 index 0000000000000..651408697169a --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 React, { FC, useCallback } from 'react'; +import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; +import { EuiListGroupItem } from '@elastic/eui'; + +import { useNavigation as useServices } from '../../../services'; +import { NavItemLabel } from './panel_nav_item_label'; +import { usePanel } from './context'; + +interface Props { + item: ChromeProjectNavigationNode; +} + +export const PanelNavItem: FC = ({ item }) => { + const { navigateToUrl } = useServices(); + const { close: closePanel } = usePanel(); + const { id, icon, deepLink, openInNewTab } = item; + const href = deepLink?.url ?? item.href; + + const onClick = useCallback( + (e) => { + if (!!href) { + e.preventDefault(); + navigateToUrl(href); + closePanel(); + } + }, + [closePanel, href, navigateToUrl] + ); + + return ( + } + wrapText + className="sideNavPanelLink" + size="s" + data-test-subj={`nav-item-id-${item.id}`} + href={href} + iconType={icon} + onClick={onClick} + target={openInNewTab ? '_blank' : undefined} + /> + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item_label.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item_label.tsx new file mode 100644 index 0000000000000..591150d1e3596 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item_label.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 React from 'react'; +import { EuiIcon } from '@elastic/eui'; +import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; + +import { LabelBadge } from './label_badge'; + +export const NavItemLabel: React.FC<{ + item: ChromeProjectNavigationNode; +}> = ({ item: { title, openInNewTab, withBadge, badgeOptions } }) => { + return ( + <> + {title} {openInNewTab && } + {withBadge && } + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/styles.ts b/packages/shared-ux/chrome/navigation/src/ui/components/panel/styles.ts new file mode 100644 index 0000000000000..3dabf39350cae --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/styles.ts @@ -0,0 +1,46 @@ +/* + * 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 { transparentize, type EuiThemeComputed } from '@elastic/eui'; +import { css } from '@emotion/css'; + +const PANEL_WIDTH = '270px'; + +export const getPanelWrapperStyles = () => css` + clip-path: polygon( + 0 0, + 150% 0, + 150% 100%, + 0 100% + ); /* Clip the left side to avoid leaking the shadow on that side */ + height: 100%; + left: calc(100% + 1px); /* Add 1 px so we see the right border */ + position: absolute; + top: 0; +`; + +export const getNavPanelStyles = (euiTheme: EuiThemeComputed<{}>) => css` + background-color: ${euiTheme.colors.body}; + height: 100%; + width: ${PANEL_WIDTH}; + + .sideNavPanelLink { + &:focus-within { + background-color: transparent; + a { + text-decoration: auto; + } + } + &:hover { + background-color: ${transparentize(euiTheme.colors.primary, 0.1)}; + a { + text-decoration: underline; + } + } + } +`; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts b/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts new file mode 100644 index 0000000000000..25b2e00c83a81 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 type { ReactNode, ComponentType } from 'react'; +import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; + +export interface PanelComponentProps { + /** Handler to close the panel */ + closePanel: () => void; + /** The node in the main panel that opens the secondary panel */ + selectedNode: PanelNavNode; + /** Jagged array of active nodes that match the current URL location */ + activeNodes: ChromeProjectNavigationNode[][]; +} + +export interface PanelContent { + title?: ReactNode | string; + content?: ComponentType; +} + +export type ContentProvider = (nodeId: string) => PanelContent | void; + +export type PanelNavNode = Pick< + ChromeProjectNavigationNode, + 'id' | 'children' | 'path' | 'sideNavStatus' +> & { + title: string | ReactNode; +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx index 9a6c9d5598a75..61b2c850851e1 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx @@ -108,10 +108,12 @@ describe('', () => { "href": "http://foo", "id": "item1", "isActive": false, + "isGroup": false, "path": Array [ "group1", "item1", ], + "sideNavStatus": "visible", "title": "Item 1", }, Object { @@ -120,10 +122,12 @@ describe('', () => { "href": "http://foo", "id": "item2", "isActive": false, + "isGroup": false, "path": Array [ "group1", "item2", ], + "sideNavStatus": "visible", "title": "Item 2", }, Object { @@ -134,11 +138,13 @@ describe('', () => { "href": "http://foo", "id": "item1", "isActive": false, + "isGroup": false, "path": Array [ "group1", "group1A", "item1", ], + "sideNavStatus": "visible", "title": "Group 1A Item 1", }, Object { @@ -149,12 +155,14 @@ describe('', () => { "href": "http://foo", "id": "item1", "isActive": false, + "isGroup": false, "path": Array [ "group1", "group1A", "group1A_1", "item1", ], + "sideNavStatus": "visible", "title": "Group 1A_1 Item 1", }, ], @@ -162,11 +170,13 @@ describe('', () => { "href": undefined, "id": "group1A_1", "isActive": false, + "isGroup": true, "path": Array [ "group1", "group1A", "group1A_1", ], + "sideNavStatus": "visible", "title": "Group1A_1", }, ], @@ -174,10 +184,12 @@ describe('', () => { "href": undefined, "id": "group1A", "isActive": false, + "isGroup": true, "path": Array [ "group1", "group1A", ], + "sideNavStatus": "visible", "title": "Group1A", }, ], @@ -185,9 +197,11 @@ describe('', () => { "href": undefined, "id": "group1", "isActive": false, + "isGroup": true, "path": Array [ "group1", ], + "sideNavStatus": "visible", "title": "", "type": "navGroup", }, @@ -274,11 +288,13 @@ describe('', () => { "href": undefined, "id": "item1", "isActive": false, + "isGroup": false, "path": Array [ "root", "group1", "item1", ], + "sideNavStatus": "visible", "title": "Title from deeplink", }, Object { @@ -293,11 +309,13 @@ describe('', () => { "href": undefined, "id": "item2", "isActive": false, + "isGroup": false, "path": Array [ "root", "group1", "item2", ], + "sideNavStatus": "visible", "title": "Overwrite deeplink title", }, ], @@ -305,10 +323,12 @@ describe('', () => { "href": undefined, "id": "group1", "isActive": false, + "isGroup": true, "path": Array [ "root", "group1", ], + "sideNavStatus": "visible", "title": "", }, ], @@ -316,9 +336,11 @@ describe('', () => { "href": undefined, "id": "root", "isActive": false, + "isGroup": true, "path": Array [ "root", ], + "sideNavStatus": "visible", "title": "", "type": "navGroup", }, @@ -375,11 +397,13 @@ describe('', () => { "href": "https://example.com", "id": "item1", "isActive": false, + "isGroup": false, "path": Array [ "root", "group1", "item1", ], + "sideNavStatus": "visible", "title": "Absolute link", }, ], @@ -387,10 +411,12 @@ describe('', () => { "href": undefined, "id": "group1", "isActive": false, + "isGroup": true, "path": Array [ "root", "group1", ], + "sideNavStatus": "visible", "title": "", }, ], @@ -398,9 +424,11 @@ describe('', () => { "href": undefined, "id": "root", "isActive": false, + "isGroup": true, "path": Array [ "root", ], + "sideNavStatus": "visible", "title": "", "type": "navGroup", }, diff --git a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx index 2457fa44a5096..f423ba98d04a7 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx @@ -6,34 +6,53 @@ * Side Public License, v 1. */ -import React, { FC, useCallback } from 'react'; +import React, { FC, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { AppDeepLinkId, NodeDefinition } from '@kbn/core-chrome-browser'; +import { getNavigationNodeId } from '../utils'; import { Navigation } from './components'; import type { GroupDefinition, - NavigationGroupPreset, + PresetDefinition, NavigationTreeDefinition, ProjectNavigationDefinition, ProjectNavigationTreeDefinition, RootNavigationItemDefinition, + RecentlyAccessedDefinition, } from './types'; import { RecentlyAccessed } from './components/recently_accessed'; import { NavigationFooter } from './components/navigation_footer'; import { getPresets } from './nav_tree_presets'; +import type { ContentProvider } from './components/panel'; -type NodeDefinitionWithPreset = NodeDefinition & { - preset?: NavigationGroupPreset; +const isPresetDefinition = ( + item: RootNavigationItemDefinition | NodeDefinition +): item is PresetDefinition => { + return (item as PresetDefinition).preset !== undefined; }; -const isRootNavigationItemDefinition = ( - item: RootNavigationItemDefinition | NodeDefinitionWithPreset -): item is RootNavigationItemDefinition => { - // Only RootNavigationItemDefinition has a "type" property - return (item as RootNavigationItemDefinition).type !== undefined; +const isGroupDefinition = ( + item: RootNavigationItemDefinition | NodeDefinition +): item is GroupDefinition => { + return ( + (item as GroupDefinition).type === 'navGroup' || (item as NodeDefinition).children !== undefined + ); +}; + +const isRecentlyAccessedDefinition = ( + item: RootNavigationItemDefinition | NodeDefinition +): item is RecentlyAccessedDefinition => { + return (item as RootNavigationItemDefinition).type === 'recentlyAccessed'; }; +/** + * Handler to build a full navigation tree definition from a project definition + * It adds all the defaults and presets (recently accessed, footer content...) + * + * @param projectDefinition The project definition + * @returns The full navigation tree definition + */ const getDefaultNavigationTree = ( projectDefinition: ProjectNavigationTreeDefinition ): NavigationTreeDefinition => { @@ -42,7 +61,13 @@ const getDefaultNavigationTree = ( { type: 'recentlyAccessed', }, - ...projectDefinition.map((def) => ({ ...def, type: 'navGroup' as const })), + ...projectDefinition.map((def) => { + if ((def as GroupDefinition).children) { + return { children: [], ...def, type: 'navGroup' as const }; + } else { + return { ...def, type: 'navItem' as const }; + } + }), { type: 'navGroup', ...getPresets('analytics'), @@ -54,7 +79,7 @@ const getDefaultNavigationTree = ( ], footer: [ { - type: 'navGroup', + type: 'navItem', id: 'devTools', title: i18n.translate('sharedUXPackages.chrome.sideNavigation.devTools', { defaultMessage: 'Developer tools', @@ -91,62 +116,104 @@ const getDefaultNavigationTree = ( }; }; -let idCounter = 0; +/** + * Serialize a navigation node. Currently this handler only adds an autogenerated id if it's missing + * + * @param item The navigation node + * @returns The navigation node serialized + */ +function serializeNode< + T extends { + id?: string; + link?: LinkId; + children?: Array<{ id?: string; link?: LinkId }>; + }, + LinkId extends AppDeepLinkId = AppDeepLinkId +>(item: T, depth: number, index: number): T & { id: string } { + const id = getNavigationNodeId(item, () => `node-${depth}-${index}`); + const children = item.children?.map((_item, i) => serializeNode(_item, depth + 1, i)); + + return { + ...item, + id, + children, + }; +} + +const serializeNavigationTree = (navTree: NavigationTreeDefinition): NavigationTreeDefinition => { + const serialized: NavigationTreeDefinition = { ...navTree }; + + const serialize = (item: RootNavigationItemDefinition, index: number) => { + if (item.type === 'recentlyAccessed') return item; + return serializeNode(item, 0, index); + }; + + if (navTree.body) { + serialized.body = navTree.body.map(serialize); + } + + if (navTree.footer) { + serialized.footer = navTree.footer.map(serialize); + } + + return serialized; +}; + +interface Props { + dataTestSubj?: string; + panelContentProvider?: ContentProvider; +} -export const DefaultNavigation: FC = ({ +export const DefaultNavigation: FC = ({ projectNavigationTree, navigationTree, dataTestSubj, + panelContentProvider, }) => { if (!navigationTree && !projectNavigationTree) { throw new Error('One of navigationTree or projectNavigationTree must be defined'); } - const navigationDefinition = !navigationTree - ? getDefaultNavigationTree(projectNavigationTree!) - : navigationTree!; - - const renderItems = useCallback( - ( - items: RootNavigationItemDefinition[] | NodeDefinitionWithPreset[] = [], - path: string[] = [] - ) => { - return items.map((item) => { - const isRootNavigationItem = isRootNavigationItemDefinition(item); - if (isRootNavigationItem && item.type === 'recentlyAccessed') { - return ; - } + const navigationDefinition = useMemo(() => { + const definition = !navigationTree + ? getDefaultNavigationTree(projectNavigationTree!) + : navigationTree; + + return serializeNavigationTree(definition); + }, [navigationTree, projectNavigationTree]); - if (item.preset) { - return ; + const renderNodes = useCallback( + (nodes: Array = []) => { + return nodes.map((navNode, i) => { + if (isPresetDefinition(navNode)) { + return ; } - const id = item.id ?? item.link; + if (isRecentlyAccessedDefinition(navNode)) { + return ; + } - if (!id) { - throw new Error( - `At least one of id or link must be defined for navigation item ${item.title}` + if (isGroupDefinition(navNode)) { + return ( + + {/* Recursively build the tree */} + {renderNodes(navNode.children)} + ); } - return item.children || (item as GroupDefinition).type === 'navGroup' ? ( - - {renderItems(item.children, [...path, id])} - - ) : ( - - ); + return ; }); }, [] ); return ( - + <> - {renderItems(navigationDefinition.body)} + {renderNodes(navigationDefinition.body)} {navigationDefinition.footer && ( - {renderItems(navigationDefinition.footer)} + {renderNodes(navigationDefinition.footer)} )} diff --git a/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts b/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts index cedfd9c1b91e9..ab0c5ae860f3c 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts @@ -6,36 +6,23 @@ * Side Public License, v 1. */ -import { +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import type { AppDeepLinkId, ChromeNavLink, ChromeProjectNavigationNode, CloudLinkId, + SideNavNodeStatus, } from '@kbn/core-chrome-browser'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import useObservable from 'react-use/lib/useObservable'; import { CloudLinks } from '../../cloud_links'; import { useNavigation as useNavigationServices } from '../../services'; -import { isAbsoluteLink } from '../../utils'; +import { getNavigationNodeId, isAbsoluteLink } from '../../utils'; import { useNavigation } from '../components/navigation'; -import { NodeProps, NodePropsEnhanced, RegisterFunction, UnRegisterFunction } from '../types'; +import { NodePropsEnhanced, RegisterFunction, UnRegisterFunction } from '../types'; import { useRegisterTreeNode } from './use_register_tree_node'; -function getIdFromNavigationNode< - LinkId extends AppDeepLinkId = AppDeepLinkId, - Id extends string = string, - ChildrenId extends string = Id ->({ id: _id, link, title }: NodeProps): string { - const id = _id ?? link; - - if (!id) { - throw new Error(`Id or link prop missing for navigation item [${title}]`); - } - - return id; -} - /** * We don't have currently a way to know if a user has access to a Cloud section. * TODO: This function will have to be revisited once we have an API from Cloud to know the user @@ -45,36 +32,36 @@ function hasUserAccessToCloudLink(): boolean { return true; } -function isNodeVisible( +function getNodeStatus( { link, deepLink, cloudLink, + sideNavStatus, }: { link?: string; deepLink?: ChromeNavLink; cloudLink?: CloudLinkId; + sideNavStatus?: SideNavNodeStatus; }, { cloudLinks }: { cloudLinks: CloudLinks } -) { +): SideNavNodeStatus | 'remove' { if (link && !deepLink) { // If a link is provided, but no deepLink is found, don't render anything - return false; + return 'remove'; } if (cloudLink) { if (!cloudLinks[cloudLink]) { // Invalid cloudLinkId or link url has not been set in kibana.yml - return false; + return 'remove'; } - return hasUserAccessToCloudLink(); + if (!hasUserAccessToCloudLink()) return 'remove'; } - if (deepLink) { - return !deepLink.hidden; - } + if (deepLink && deepLink.hidden) return 'remove'; - return true; + return sideNavStatus ?? 'visible'; } function getTitleForNode< @@ -109,12 +96,34 @@ function validateNodeProps< LinkId extends AppDeepLinkId = AppDeepLinkId, Id extends string = string, ChildrenId extends string = Id ->({ link, href, cloudLink }: NodePropsEnhanced) { +>({ + id, + link, + href, + cloudLink, + renderAs, + appendHorizontalRule, + isGroup, +}: Omit, 'children'>) { if (link && cloudLink) { - throw new Error(`Only one of "link" or "cloudLink" can be provided.`); + throw new Error( + `[Chrome navigation] Error in node [${id}]. Only one of "link" or "cloudLink" can be provided.` + ); } if (href && cloudLink) { - throw new Error(`Only one of "href" or "cloudLink" can be provided.`); + throw new Error( + `[Chrome navigation] Error in node [${id}]. Only one of "href" or "cloudLink" can be provided.` + ); + } + if (renderAs === 'panelOpener' && !link) { + throw new Error( + `[Chrome navigation] Error in node [${id}]. If renderAs is set to "panelOpener", a "link" must also be provided.` + ); + } + if (appendHorizontalRule && !isGroup) { + throw new Error( + `[Chrome navigation] Error in node [${id}]. "appendHorizontalRule" can only be added for group with children.` + ); } } @@ -134,7 +143,15 @@ function createInternalNavNode< const { children, link, cloudLink, ...navNode } = _navNode; const deepLink = deepLinks.find((dl) => dl.id === link); - const isVisible = isNodeVisible({ link, deepLink, cloudLink }, { cloudLinks }); + const sideNavStatus = getNodeStatus( + { + link, + deepLink, + cloudLink, + sideNavStatus: navNode.sideNavStatus, + }, + { cloudLinks } + ); const title = getTitleForNode(_navNode, { deepLink, cloudLinks }); const href = cloudLink ? cloudLinks[cloudLink]?.href : _navNode.href; @@ -142,18 +159,19 @@ function createInternalNavNode< throw new Error(`href must be an absolute URL. Node id [${id}].`); } - if (!isVisible) { + if (sideNavStatus === 'remove') { return null; } return { ...navNode, id, - path: path ?? [id], + path: path ?? [], title: title ?? '', deepLink, href, isActive, + sideNavStatus, }; } @@ -171,7 +189,7 @@ export const useInitNavNode = < Id extends string = string, ChildrenId extends string = Id >( - node: NodePropsEnhanced, + node: Omit, 'children'>, { cloudLinks }: { cloudLinks: CloudLinks } ) => { const { isActive: isActiveControlled } = node; @@ -215,7 +233,7 @@ export const useInitNavNode = < const { register: registerNodeOnParent } = useRegisterTreeNode(); const { activeNodes } = useNavigation(); - const id = getIdFromNavigationNode(node); + const id = getNavigationNodeId(node); const internalNavNode = useMemo( () => createInternalNavNode(id, node, deepLinks, nodePath, isActive, { cloudLinks }), diff --git a/packages/shared-ux/chrome/navigation/src/ui/index.ts b/packages/shared-ux/chrome/navigation/src/ui/index.ts index 1335a1844f45c..60b55fc5d200a 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/index.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/index.ts @@ -7,6 +7,7 @@ */ export { Navigation } from './components'; +export type { PanelContent, PanelComponentProps } from './components'; export { DefaultNavigation } from './default_navigation'; @@ -14,6 +15,8 @@ export { getPresets } from './nav_tree_presets'; export type { GroupDefinition, + PresetDefinition, + ItemDefinition, NavigationGroupPreset, NavigationTreeDefinition, ProjectNavigationDefinition, diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index c014678265ce9..95a5dbe163668 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -7,9 +7,9 @@ */ import { action } from '@storybook/addon-actions'; -import { useState } from '@storybook/addons'; +import { useState as useStateStorybook } from '@storybook/addons'; import { ComponentMeta } from '@storybook/react'; -import React, { EventHandler, FC, PropsWithChildren, MouseEvent } from 'react'; +import React, { EventHandler, FC, MouseEvent, useState, useEffect } from 'react'; import { BehaviorSubject, of } from 'rxjs'; import { @@ -34,19 +34,51 @@ import { NavigationProvider } from '../services'; import { Navigation } from './components'; import { DefaultNavigation } from './default_navigation'; import { getPresets } from './nav_tree_presets'; -import type { GroupDefinition, NonEmptyArray, ProjectNavigationDefinition } from './types'; +import type { GroupDefinition, ProjectNavigationDefinition } from './types'; +import { ContentProvider } from './components/panel'; const storybookMock = new NavigationStorybookMock(); -const NavigationWrapper: FC< - PropsWithChildren<{ clickAction?: EventHandler; clickActionText?: string }> & - Partial -> = (props) => { +interface Props { + clickAction?: EventHandler; + clickActionText?: string; + children?: React.ReactNode | (({ isCollapsed }: { isCollapsed: boolean }) => React.ReactNode); +} + +const NavigationWrapper: FC> = (props) => { + const [isCollapsed, setIsCollapsed] = useState(false); + + const onCollapseToggle = (nextIsCollapsed: boolean) => { + setIsCollapsed(nextIsCollapsed); + }; + + useEffect(() => { + // Set padding to body to avoid unnecessary scrollbars + document.body.style.paddingTop = '0px'; + document.body.style.paddingRight = '0px'; + document.body.style.paddingBottom = '0px'; + }, []); + return ( <> - + @@ -68,7 +100,7 @@ const baseDeeplink: ChromeNavLink = { id: 'foo', title: 'Title from deep link', href: 'https://elastic.co', - url: '', + url: '/dashboard-mocked', baseUrl: '', }; @@ -83,15 +115,19 @@ const createDeepLink = (id: string, title: string = baseDeeplink.title) => { const deepLinks: ChromeNavLink[] = [ createDeepLink('item1'), createDeepLink('item2', 'Foo'), + createDeepLink('item3'), createDeepLink('group1:item1'), createDeepLink('group1:groupA:groupI:item1'), createDeepLink('group1:groupA', 'Group title from deep link'), createDeepLink('group2', 'Group title from deep link'), createDeepLink('group2:item1'), createDeepLink('group2:item3'), + createDeepLink('group:settings.logs'), + createDeepLink('group:settings.signals'), + createDeepLink('group:settings.tracing'), ]; -const simpleNavigationDefinition: ProjectNavigationDefinition = { +const simpleNavigationDefinition: ProjectNavigationDefinition = { projectNavigationTree: [ { id: 'example_projet', @@ -100,15 +136,15 @@ const simpleNavigationDefinition: ProjectNavigationDefinition = { defaultIsCollapsed: false, children: [ { - id: 'item1', + link: 'item1', title: 'Get started', }, { - id: 'item2', + link: 'item2', title: 'Alerts', }, { - id: 'item3', + link: 'item3', title: 'Dashboards', }, { @@ -117,7 +153,7 @@ const simpleNavigationDefinition: ProjectNavigationDefinition = { href: 'https://elastic.co', }, { - id: 'item5', + link: 'item5', title: 'Another link', }, { @@ -125,15 +161,15 @@ const simpleNavigationDefinition: ProjectNavigationDefinition = { title: 'Settings', children: [ { - id: 'logs', + link: 'item1', title: 'Logs', }, { - id: 'signals', + link: 'item2', title: 'Signals', }, { - id: 'tracing', + link: 'item3', title: 'Tracing', }, ], @@ -165,7 +201,7 @@ export const SimpleObjectDefinition = (args: NavigationServices) => { ); }; -const navigationDefinition: ProjectNavigationDefinition = { +const navigationDefinition: ProjectNavigationDefinition = { navigationTree: { body: [ // My custom project @@ -177,42 +213,158 @@ const navigationDefinition: ProjectNavigationDefinition = { defaultIsCollapsed: false, children: [ { - id: 'item1', + link: 'item1', title: 'Get started', }, { - id: 'item2', + link: 'item2', title: 'Alerts', }, { - id: 'item3', + id: 'item2-2', + link: 'item2', + title: 'Item should NOT appear!!', + sideNavStatus: 'hidden', // Should not appear + }, + { + link: 'item3', title: 'Some other node', }, { - id: 'group:settings', - title: 'Settings', + id: 'group:settings-2', + title: 'Settings as nav Item', + renderAs: 'item', // Render just like any other item, even if it has children children: [ { - id: 'logs', + link: 'group:settings.logs', title: 'Logs', }, { - id: 'signals', + link: 'group:settings.signals', title: 'Signals', }, { - id: 'tracing', + id: 'group:settings.signals2', + link: 'group:settings.signals', + title: 'Signals - should NOT appear', + sideNavStatus: 'hidden', // Should not appear + }, + { + link: 'group:settings.tracing', title: 'Tracing', }, ], }, + { + id: 'group:settings', + link: 'item1', + title: 'Settings as panel opener', + renderAs: 'panelOpener', + children: [ + { + title: 'Group 1', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + { + id: 'group:settings.signals-2', + link: 'group:settings.signals', + title: 'Signals - should NOT appear', + sideNavStatus: 'hidden', // Should not appear + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + { + children: [ + { + id: 'group.nestedGroup', + link: 'group:settings.tracing', + title: 'Group 2', + renderAs: 'item', + children: [ + { + id: 'item1', + link: 'group:settings.signals', + title: 'Hidden - should NOT appear', + sideNavStatus: 'hidden', // Should not appear + }, + ], + }, + ], + }, + { + children: [ + { + id: 'group.nestedGroup', + link: 'group:settings.tracing', + title: 'Just an item in a group', + }, + ], + }, + ], + }, + { + id: 'group:settings.hidden', + title: 'Settings 1 - should NOT appear', // sideNavStatus is 'hidden' + sideNavStatus: 'hidden', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + ], + }, + { + id: 'group:settings.childrenHidden', + link: 'item1', + title: 'Settings 2 - should NOT appear', // All children are hidden + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + sideNavStatus: 'hidden', + }, + ], + }, ], }, // Add ml { - type: 'navGroup', + type: 'preset', preset: 'ml', }, + { + type: 'navItem', + title: 'Custom link at root level', + }, + { + type: 'navGroup', + id: 'test_all_hidden', + title: 'Test group render as Item', + renderAs: 'item', + children: [ + { + id: 'test.item1', + link: 'item1', + }, + ], + }, + { + type: 'navItem', + icon: 'logoElastic', + link: 'ml', + title: 'Link at root level + icon', + }, // And specific links from analytics { type: 'navGroup', @@ -224,7 +376,7 @@ const navigationDefinition: ProjectNavigationDefinition = { // Hide discover and dashboard return item.link !== 'discover' && item.link !== 'dashboards'; }), - })) as NonEmptyArray, + })), }, ], footer: [ @@ -275,7 +427,505 @@ export const ComplexObjectDefinition = (args: NavigationServices) => { ); }; -export const WithUIComponents = (args: NavigationServices) => { +const panelContentProvider: ContentProvider = (id: string) => { + if (id === 'example_projet.group:openpanel1') { + return; // Use default title & content + } + + if (id === 'example_projet.group:openpanel2') { + // Custom content + return { + content: ({ closePanel }) => { + return ( +
      + This is a custom component to render in the panel. + closePanel()}>Close panel +
      + ); + }, + }; + } + + if (id === 'example_projet.group:openpanel3') { + return { + title:
      Custom title
      , + }; + } +}; + +const navigationDefinitionWithPanel: ProjectNavigationDefinition = { + navigationTree: { + body: [ + // My custom project + { + type: 'navGroup', + id: 'example_projet', + title: 'Example project', + icon: 'logoObservability', + defaultIsCollapsed: false, + accordionProps: { + arrowProps: { css: { display: 'none' } }, + }, + children: [ + { + link: 'item1', + title: 'Get started', + }, + { + link: 'item2', + title: 'Alerts', + }, + { + // Panel with default content + // Groups with title + id: 'group:openpanel1', + title: 'Open panel (1)', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + title: 'Group 1', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + icon: 'logoObservability', + }, + { + link: 'group:settings.signals', + title: 'Signals', + openInNewTab: true, + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + withBadge: true, // Default to "Beta" badge + }, + ], + }, + { + title: 'Group 2', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + { + id: 'group2:settings.tracing', + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + ], + }, + { + // Panel with default content + // Groups with **not** title + id: 'group.openpanel2', + title: 'Open panel (2)', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + appendHorizontalRule: true, // Add a separator after the group + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + withBadge: true, // Default to "Beta" badge + }, + ], + }, + { + id: 'group2', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + { + id: 'group2:settings.tracing', + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + ], + }, + { + // Panel with default content + // Accordion to wrap groups + id: 'group.openpanel3', + title: 'Open panel (3)', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + id: 'group1', + appendHorizontalRule: true, + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + withBadge: true, // Default to "Beta" badge + }, + ], + }, + // Groups with accordion + { + id: 'group2', + title: 'MANAGEMENT', + renderAs: 'accordion', + children: [ + { + id: 'group2-A', + title: 'Group 1', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + withBadge: true, // Default to "Beta" badge + }, + ], + }, + { + id: 'group2-B', + title: 'Group 2 (marked as collapsible)', + renderAs: 'accordion', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + { + id: 'group2:settings.tracing', + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + { + id: 'group2-C', + title: 'Group 3', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + { + id: 'group2:settings.tracing', + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + ], + }, + ], + }, + { + // Panel with nav group title that acts like nav items + id: 'group.openpanel4', + title: 'Open panel (4) - sideNavStatus', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + id: 'root', + children: [ + { + title: 'Should act as item 1', + link: 'item1', + renderAs: 'item', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + ], + }, + { + link: 'group:settings.logs', + title: 'Normal item', + }, + { + link: 'group:settings.logs2', + title: 'Item should NOT appear!', // Should not appear + sideNavStatus: 'hidden', + }, + { + title: 'Group should NOT appear!', + id: 'group:settings.logs3', + link: 'group:settings.logs', + sideNavStatus: 'hidden', // This group should not appear + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + ], + }, + { + title: 'Should act as item 2', + renderAs: 'item', // This group renders as a normal item + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + ], + }, + ], + }, + { + children: [ + { + title: 'Another group as Item', + id: 'group2.renderAsItem', + renderAs: 'item', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + sideNavStatus: 'hidden', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + sideNavStatus: 'hidden', + }, + { + id: 'group2:settings.tracing', + link: 'group:settings.tracing', + title: 'Tracing', + sideNavStatus: 'hidden', + }, + ], + }, + ], + }, + // Groups with accordion + { + id: 'group2', + title: 'MANAGEMENT', + renderAs: 'accordion', + children: [ + { + id: 'group2-A', + title: 'Group 1', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + withBadge: true, // Default to "Beta" badge + }, + ], + }, + { + id: 'root-groupB', + children: [ + { + id: 'group2-B', + title: 'Group 2 (render as Item)', + renderAs: 'item', // This group renders as a normal item + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + { + id: 'group2:settings.tracing', + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + ], + }, + { + id: 'group2-C', + title: 'Group 3', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + title: 'Yet another group as item', + renderAs: 'item', + children: [ + { + id: 'group2:settings.logs', + link: 'group:settings.logs', + title: 'Logs', + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + ], + }, + { + id: 'group2:settings.signals', + link: 'group:settings.signals', + title: 'Signals', + }, + ], + }, + ], + }, + ], + }, + { + // Panel where all children are hidden. The "open panel" icon should NOT + // appear next to the node title + id: 'group.openpanel5', + title: 'Open panel (5) - all children hidden', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + link: 'test1', + sideNavStatus: 'hidden', + }, + { + title: 'Some group', + children: [ + { + link: 'item1', + title: 'My first group item', + sideNavStatus: 'hidden', + }, + ], + }, + ], + }, + { + id: 'group.openpanel6', + title: 'Open panel (custom content)', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + link: 'group:settings.logs', + title: 'Logs', + }, + { + link: 'group:settings.signals', + title: 'Signals', + }, + { + link: 'group:settings.tracing', + title: 'Tracing', + }, + ], + }, + { + id: 'group.openpanel7', + title: 'Open panel (custom title)', + renderAs: 'panelOpener', + link: 'item1', + children: [ + { + link: 'group:settings.logs', + title: 'Those links', + }, + { + link: 'group:settings.signals', + title: 'are automatically', + }, + { + link: 'group:settings.tracing', + title: 'generated', + }, + ], + }, + ], + }, + ], + }, +}; + +export const ObjectDefinitionWithPanel = (args: NavigationServices) => { const services = storybookMock.getServices({ ...args, navLinks$: of([...navLinksMock, ...deepLinks]), @@ -290,59 +940,122 @@ export const WithUIComponents = (args: NavigationServices) => { return ( - - - - - - id="item1" link="item1" /> - - {(navNode) => { - return ( -
      - {`Render prop: ${navNode.id} - ${navNode.title}`} -
      - ); - }} -
      - -
      - Title in ReactNode -
      -
      - + {({ isCollapsed }) => ( + + + + )} +
      + ); +}; - - - - - -
      +export const WithUIComponents = (args: NavigationServices) => { + const services = storybookMock.getServices({ + ...args, + navLinks$: of([...navLinksMock, ...deepLinks]), + onProjectNavigationChange: (updated) => { + action('Update chrome navigation')(JSON.stringify(updated, null, 2)); + }, + recentlyAccessed$: of([ + { label: 'This is an example', link: '/app/example/39859', id: '39850' }, + { label: 'Another example', link: '/app/example/5235', id: '5235' }, + ]), + }); - - + return ( + + {({ isCollapsed }) => ( + + + - - - - - - + id="item1" link="item1" /> + + {(navNode) => { + return ( +
      + {`Render prop: ${navNode.id} - ${navNode.title}`} +
      + ); + }} +
      + +
      + Title in ReactNode +
      +
      + + + + id="group:openPanel" + link="item1" + title="Open panel" + renderAs="panelOpener" + > + + link="group:settings.logs" title="Logs" /> + link="group:settings.signals" title="Signals" withBadge /> + link="group:settings.tracing" title="Tracing" /> + + + link="group:settings.logs" title="Logs" /> + link="group:settings.signals" title="Signals" /> + link="group:settings.tracing" title="Tracing" /> + + + + link="group:settings.logs" title="Logs" /> + + link="group:settings.signals" + title="Signals" + withBadge + badgeOptions={{ text: 'coolio' }} + /> + link="group:settings.tracing" title="Tracing" /> + + + link="group:settings.logs" title="Logs" /> + link="group:settings.signals" title="Signals" /> + link="group:settings.tracing" title="Tracing" /> + + + link="group:settings.logs" title="Logs" /> + link="group:settings.signals" title="Signals" /> + link="group:settings.tracing" title="Tracing" /> + + +
      -
      -
      -
      + + + + + + + + + + + + + + + + )}
      ); }; @@ -398,17 +1111,6 @@ export const MinimalUI = (args: NavigationServices) => { ); }; -export default { - title: 'Chrome/Navigation', - description: 'Navigation container to render items for cross-app linking', - parameters: { - docs: { - page: mdx, - }, - }, - component: WithUIComponents, -} as ComponentMeta; - export const CreativeUI = (args: NavigationServices) => { const services = storybookMock.getServices({ ...args, @@ -602,7 +1304,7 @@ export const UpdatingState = (args: NavigationServices) => { ]; // use state to track which element of activeNodeSets is active - const [activeNodeIndex, setActiveNodeIndex] = useState(0); + const [activeNodeIndex, setActiveNodeIndex] = useStateStorybook(0); const changeActiveNode = () => { const value = (activeNodeIndex + 1) % 2; // toggle between 0 and 1 setActiveNodeIndex(value); @@ -632,3 +1334,14 @@ export const UpdatingState = (args: NavigationServices) => { ); }; + +export default { + title: 'Chrome/Navigation', + description: 'Navigation container to render items for cross-app linking', + parameters: { + docs: { + page: mdx, + }, + }, + component: WithUIComponents, +} as ComponentMeta; diff --git a/packages/shared-ux/chrome/navigation/src/ui/types.ts b/packages/shared-ux/chrome/navigation/src/ui/types.ts index e7c642fc9d0b9..c9caa116c8b97 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/types.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/types.ts @@ -16,8 +16,6 @@ import type { } from '@kbn/core-chrome-browser'; import type { RecentlyAccessedProps } from './components'; -export type NonEmptyArray = [T, ...T[]]; - /** * @public * @@ -51,6 +49,8 @@ export interface NodePropsEnhanced< * even if the URL does not match any of the nodes in the group. */ isActive?: boolean; + /** Flag to indicate if the navigation node is a group or not */ + isGroup: boolean; } /** The preset that can be pass to the NavigationBucket component */ @@ -74,7 +74,7 @@ export interface GroupDefinition< LinkId extends AppDeepLinkId = AppDeepLinkId, Id extends string = string, ChildrenId extends string = Id -> extends NodeDefinition { +> extends Omit, 'children'> { type: 'navGroup'; /** * Flag to indicate if the group is initially collapsed or not. @@ -90,7 +90,34 @@ export interface GroupDefinition< * Pass props to the EUI accordion component used to represent a nav group */ accordionProps?: Partial; - preset?: NavigationGroupPreset; + children: Array>; +} + +/** + * @public + * + * A group root item definition built from a specific preset. + */ +export interface PresetDefinition< + LinkId extends AppDeepLinkId = AppDeepLinkId, + Id extends string = string, + ChildrenId extends string = Id +> extends Omit, 'children' | 'type'> { + type: 'preset'; + preset: NavigationGroupPreset; +} + +/** + * @public + * + * An item root. + */ +export interface ItemDefinition< + LinkId extends AppDeepLinkId = AppDeepLinkId, + Id extends string = string, + ChildrenId extends string = Id +> extends Omit, 'children'> { + type: 'navItem'; } /** @@ -102,30 +129,41 @@ export type RootNavigationItemDefinition< LinkId extends AppDeepLinkId = AppDeepLinkId, Id extends string = string, ChildrenId extends string = Id -> = RecentlyAccessedDefinition | GroupDefinition; +> = + | RecentlyAccessedDefinition + | GroupDefinition + | PresetDefinition + | ItemDefinition; export type ProjectNavigationTreeDefinition< LinkId extends AppDeepLinkId = AppDeepLinkId, Id extends string = string, ChildrenId extends string = Id -> = Array, 'type'>>; +> = Array< + | Omit, 'type'> + | Omit, 'type'> +>; /** * @public * * Definition for the complete navigation tree, including body and footer */ -export interface NavigationTreeDefinition { +export interface NavigationTreeDefinition< + LinkId extends AppDeepLinkId = AppDeepLinkId, + Id extends string = string, + ChildrenId extends string = Id +> { /** * Main content of the navigation. Can contain any number of "cloudLink", "recentlyAccessed" * or "group" items. Be mindeful though, with great power comes great responsibility. * */ - body?: RootNavigationItemDefinition[]; + body?: Array>; /** * Footer content of the navigation. Can contain any number of "cloudLink", "recentlyAccessed" * or "group" items. Be mindeful though, with great power comes great responsibility. * */ - footer?: RootNavigationItemDefinition[]; + footer?: Array>; } /** @@ -134,19 +172,23 @@ export interface NavigationTreeDefinition { * A project navigation definition that can be passed to the `` component * or when calling `setNavigation()` on the serverless plugin. */ -export interface ProjectNavigationDefinition { +export interface ProjectNavigationDefinition< + LinkId extends AppDeepLinkId = AppDeepLinkId, + Id extends string = string, + ChildrenId extends string = Id +> { /** * A navigation tree structure with object items containing labels, links, and sub-items * for a project. Use it if you only need to configure your project navigation and leave * all the other navigation items to the default (Recently viewed items, Management, etc.) */ - projectNavigationTree?: ProjectNavigationTreeDefinition; + projectNavigationTree?: ProjectNavigationTreeDefinition; /** * A navigation tree structure with object items containing labels, links, and sub-items * that defines a complete side navigation. This configuration overrides `projectNavigationTree` * if both are provided. */ - navigationTree?: NavigationTreeDefinition; + navigationTree?: NavigationTreeDefinition; } /** diff --git a/packages/shared-ux/chrome/navigation/src/utils.ts b/packages/shared-ux/chrome/navigation/src/utils.ts index 9458d2b6fc283..8322fe797590b 100644 --- a/packages/shared-ux/chrome/navigation/src/utils.ts +++ b/packages/shared-ux/chrome/navigation/src/utils.ts @@ -6,6 +6,47 @@ * Side Public License, v 1. */ +import type { ChromeProjectNavigationNode, NodeDefinition } from '@kbn/core-chrome-browser'; + +let uniqueId = 0; + +function generateUniqueNodeId() { + const id = `node${uniqueId++}`; + return id; +} + export function isAbsoluteLink(link: string) { return link.startsWith('http://') || link.startsWith('https://'); } + +export function nodePathToString( + node?: T +): T extends { path?: string[]; id: string } ? string : undefined { + if (!node) return undefined as T extends { path?: string[]; id: string } ? string : undefined; + return (node.path ? node.path.join('.') : node.id) as T extends { path?: string[]; id: string } + ? string + : undefined; +} + +export function isGroupNode({ children }: Pick) { + return children !== undefined; +} + +export function isItemNode({ children }: Pick) { + return children === undefined; +} + +export function getNavigationNodeId( + { id: _id, link }: Pick, + idGenerator = generateUniqueNodeId +): string { + const id = _id ?? link; + return id ?? idGenerator(); +} + +export function getNavigationNodeHref({ + href, + deepLink, +}: Pick): string | undefined { + return deepLink?.url ?? href; +} diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 00d8e69edf0e3..e162e395362a9 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -30,6 +30,7 @@ export interface NavigationServices { onProjectNavigationChange: (chromeProjectNavigation: ChromeProjectNavigation) => void; activeNodes$: Observable; cloudLinks: CloudLinks; + isSideNavCollapsed: boolean; } /** @@ -45,6 +46,7 @@ export interface NavigationKibanaDependencies { navLinks: { getNavLinks$: () => Observable>; }; + getIsSideNavCollapsed$: () => Observable; }; http: { basePath: BasePathService; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts index 0db77598e7032..926e54ad8bb33 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import type { NavigationTreeDefinition } from '@kbn/shared-ux-chrome-navigation'; import type { AppDeepLinkId, NodeDefinition } from '@kbn/core-chrome-browser'; -import type { NonEmptyArray } from '@kbn/shared-ux-chrome-navigation/src/ui/types'; import type { LinkCategory } from '@kbn/security-solution-navigation'; import { SecurityPageName, @@ -37,72 +36,83 @@ const PROJECT_SETTINGS_TITLE = i18n.translate( export const formatNavigationTree = ( projectNavLinks: ProjectNavigationLink[], categories?: Readonly>> -): NavigationTreeDefinition => ({ - body: [ - { - type: 'navGroup', - id: 'security_project_nav', - title: SECURITY_TITLE, - icon: 'logoSecurity', - breadcrumbStatus: 'hidden', - defaultIsCollapsed: false, - children: formatNodesFromLinks(projectNavLinks, categories), - }, - ], - footer: [ - { - type: 'navGroup', - id: 'getStarted', - title: GET_STARTED_TITLE, - link: getNavLinkIdFromProjectPageName(SecurityPageName.landing) as AppDeepLinkId, - icon: 'launch', - }, - { - type: 'navGroup', - id: 'devTools', - title: DEV_TOOLS_TITLE, - link: 'dev_tools', - icon: 'editorCodeBlock', - }, - { - type: 'navGroup', - id: 'project_settings_project_nav', - title: PROJECT_SETTINGS_TITLE, - icon: 'gear', - breadcrumbStatus: 'hidden', - children: [ - { - id: 'settings', - children: [ - { - link: 'management', - title: 'Management', - }, - { - link: 'integrations', - }, - { - link: 'fleet', - }, - { - id: 'cloudLinkUserAndRoles', - cloudLink: 'userAndRoles', - }, - { - id: 'cloudLinkBilling', - cloudLink: 'billingAndSub', - }, - ], - }, - ], - }, - ], -}); +): NavigationTreeDefinition => { + const children = formatNodesFromLinks(projectNavLinks, categories); + return { + body: [ + children + ? { + type: 'navGroup', + id: 'security_project_nav', + title: SECURITY_TITLE, + icon: 'logoSecurity', + breadcrumbStatus: 'hidden', + defaultIsCollapsed: false, + children, + } + : { + type: 'navItem', + id: 'security_project_nav', + title: SECURITY_TITLE, + icon: 'logoSecurity', + breadcrumbStatus: 'hidden', + }, + ], + footer: [ + { + type: 'navItem', + id: 'getStarted', + title: GET_STARTED_TITLE, + link: getNavLinkIdFromProjectPageName(SecurityPageName.landing) as AppDeepLinkId, + icon: 'launch', + }, + { + type: 'navItem', + id: 'devTools', + title: DEV_TOOLS_TITLE, + link: 'dev_tools', + icon: 'editorCodeBlock', + }, + { + type: 'navGroup', + id: 'project_settings_project_nav', + title: PROJECT_SETTINGS_TITLE, + icon: 'gear', + breadcrumbStatus: 'hidden', + children: [ + { + id: 'settings', + children: [ + { + link: 'management', + title: 'Management', + }, + { + link: 'integrations', + }, + { + link: 'fleet', + }, + { + id: 'cloudLinkUserAndRoles', + cloudLink: 'userAndRoles', + }, + { + id: 'cloudLinkBilling', + cloudLink: 'billingAndSub', + }, + ], + }, + ], + }, + ], + }; +}; const formatNodesFromLinks = ( projectNavLinks: ProjectNavigationLink[], parentCategories?: Readonly>> -): NonEmptyArray | undefined => { +): NodeDefinition[] | undefined => { if (projectNavLinks.length === 0) { return undefined; } @@ -117,7 +127,7 @@ const formatNodesFromLinks = ( if (nodes.length === 0) { return undefined; } - return nodes as NonEmptyArray; + return nodes as NodeDefinition[]; }; const formatNodesFromLinksWithCategory = ( @@ -142,7 +152,7 @@ const formatNodesFromLinksWithCategory = ( { id: `category-${category.label.toLowerCase().replace(' ', '_')}`, title: category.label, - children: children as NonEmptyArray, + children: children as NodeDefinition[], }, ]; } else if (isSeparatorLinkCategory(category)) { @@ -165,7 +175,7 @@ const formatNodesFromLinksWithCategory = ( const formatNodesFromLinksWithoutCategory = (projectNavLinks: ProjectNavigationLink[]) => projectNavLinks.map((projectNavLink) => createNodeFromProjectNavLink(projectNavLink) - ) as NonEmptyArray; + ) as NodeDefinition[]; const createNodeFromProjectNavLink = (projectNavLink: ProjectNavigationLink): NodeDefinition => { const { id, title, links, categories } = projectNavLink; diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx index 47e459c9c8cea..d2b07282e70b5 100644 --- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx @@ -172,7 +172,7 @@ const navigationTree: NavigationTreeDefinition = { ], footer: [ { - type: 'navGroup', + type: 'navItem', title: i18n.translate('xpack.serverlessObservability.nav.getStarted', { defaultMessage: 'Get Started', }), @@ -181,7 +181,7 @@ const navigationTree: NavigationTreeDefinition = { icon: 'launch', }, { - type: 'navGroup', + type: 'navItem', id: 'devTools', title: i18n.translate('xpack.serverlessObservability.nav.devTools', { defaultMessage: 'Developer tools', diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx index 7e050fe4fdc1e..a4078ba841861 100644 --- a/x-pack/plugins/serverless_search/public/layout/nav.tsx +++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx @@ -114,7 +114,7 @@ const navigationTree: NavigationTreeDefinition = { ], footer: [ { - type: 'navGroup', + type: 'navItem', id: 'search_getting_started', title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', { defaultMessage: 'Getting started', diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts index 56351af9b43f1..a5f6a03329037 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts @@ -19,6 +19,8 @@ type NavigationId = MlNavId | AlNavId | MgmtNavId | DevNavId | string; import type { FtrProviderContext } from '../ftr_provider_context'; import type { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; +const getSectionIdTestSubj = (sectionId: NavigationId) => `~nav-item-${sectionId} `; + export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { const testSubjects = ctx.getService('testSubjects'); const browser = ctx.getService('browser'); @@ -95,11 +97,11 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { }, async expectSectionExists(sectionId: NavigationId) { log.debug('ServerlessCommonNavigation.sidenav.expectSectionExists', sectionId); - await testSubjects.existOrFail(`~nav-bucket-${sectionId}`); + await testSubjects.existOrFail(getSectionIdTestSubj(sectionId)); }, async isSectionOpen(sectionId: NavigationId) { await this.expectSectionExists(sectionId); - const section = await testSubjects.find(`~nav-bucket-${sectionId}`); + const section = await testSubjects.find(getSectionIdTestSubj(sectionId)); const collapseBtn = await section.findByCssSelector( `[aria-controls="${sectionId}"][aria-expanded]` ); @@ -126,7 +128,7 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { await this.expectSectionExists(sectionId); const isOpen = await this.isSectionOpen(sectionId); if (isOpen) return; - const section = await testSubjects.find(`~nav-bucket-${sectionId}`); + const section = await testSubjects.find(getSectionIdTestSubj(sectionId)); const collapseBtn = await section.findByCssSelector( `[aria-controls="${sectionId}"][aria-expanded]` ); @@ -137,7 +139,7 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { await this.expectSectionExists(sectionId); const isOpen = await this.isSectionOpen(sectionId); if (!isOpen) return; - const section = await testSubjects.find(`~nav-bucket-${sectionId}`); + const section = await testSubjects.find(getSectionIdTestSubj(sectionId)); const collapseBtn = await section.findByCssSelector( `[aria-controls="${sectionId}"][aria-expanded]` ); @@ -192,10 +194,10 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { search: new SvlNavigationSearchPageObject(ctx), recent: { async expectExists() { - await testSubjects.existOrFail('nav-bucket-recentlyAccessed'); + await testSubjects.existOrFail('nav-item-recentlyAccessed'); }, async expectHidden() { - await testSubjects.missingOrFail('nav-bucket-recentlyAccessed', { timeout: 1000 }); + await testSubjects.missingOrFail('nav-item-recentlyAccessed', { timeout: 1000 }); }, async expectLinkExists(text: string) { await this.expectExists(); @@ -203,7 +205,7 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { await retry.try(async () => { foundLink = await getByVisibleText( async () => - (await testSubjects.find('nav-bucket-recentlyAccessed')).findAllByTagName('a'), + (await testSubjects.find('nav-item-recentlyAccessed')).findAllByTagName('a'), text ); expect(!!foundLink).to.be(true); diff --git a/x-pack/test_serverless/functional/services/ml/observability_navigation.ts b/x-pack/test_serverless/functional/services/ml/observability_navigation.ts index f09467263deff..ec26c87be1d27 100644 --- a/x-pack/test_serverless/functional/services/ml/observability_navigation.ts +++ b/x-pack/test_serverless/functional/services/ml/observability_navigation.ts @@ -11,9 +11,11 @@ export function MachineLearningNavigationProviderObservability({ getService }: F const testSubjects = getService('testSubjects'); async function navigateToArea(id: string) { - await testSubjects.click('~nav-item-id-aiops'); - await testSubjects.existOrFail(`~nav-item-id-ml:${id}`, { timeout: 60 * 1000 }); - await testSubjects.click(`~nav-item-id-ml:${id}`); + await testSubjects.click('~nav-item-id-observability_project_nav.aiops'); + await testSubjects.existOrFail(`~nav-item-id-observability_project_nav.aiops.ml:${id}`, { + timeout: 60 * 1000, + }); + await testSubjects.click(`~nav-item-id-observability_project_nav.aiops.ml:${id}`); } return { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts index 22b59a9e3ed05..b3d6c225f50c4 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts @@ -96,22 +96,31 @@ describe.skip('Serverless', () => { it('sets service nav item as active', () => { cy.visitKibana('/app/apm/service-groups'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:services').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:services').should( 'have.class', 'euiSideNavItemButton-isSelected' ); cy.visitKibana('/app/apm/service-maps'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:services').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:services').should( 'have.class', 'euiSideNavItemButton-isSelected' ); cy.visitKibana('/app/apm/mobile-services/foo'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:services').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:services').should( 'have.class', 'euiSideNavItemButton-isSelected' ); @@ -119,15 +128,21 @@ describe.skip('Serverless', () => { it('sets dependencies nav item as active', () => { cy.visitKibana('/app/apm/dependencies/inventory'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:dependencies').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:dependencies').should( 'have.class', 'euiSideNavItemButton-isSelected' ); cy.visitKibana('/app/apm/dependencies/operations?dependencyName=foo'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:dependencies').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:dependencies').should( 'have.class', 'euiSideNavItemButton-isSelected' ); @@ -136,15 +151,21 @@ describe.skip('Serverless', () => { it('sets traces nav item as active', () => { cy.visitKibana('/app/apm/traces/explorer/waterfall'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:traces').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:traces').should( 'have.class', 'euiSideNavItemButton-isSelected' ); cy.visitKibana('/app/apm/traces/explorer/critical_path'); - cy.getByTestSubj('nav-item-id-apm').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-apm:traces').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.apm').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.apm.apm:traces').should( 'have.class', 'euiSideNavItemButton-isSelected' ); @@ -153,15 +174,21 @@ describe.skip('Serverless', () => { it('sets AIOps nav item as active', () => { cy.visitKibana('/app/ml/aiops/explain_log_rate_spikes'); - cy.getByTestSubj('nav-item-id-aiops').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-ml:logRateAnalysis').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.aiops').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.aiops.ml:logRateAnalysis').should( 'have.class', 'euiSideNavItemButton-isSelected' ); cy.visitKibana('/app/ml/aiops/change_point_detection'); - cy.getByTestSubj('nav-item-id-aiops').should('have.class', 'euiSideNavItemButton-isOpen'); - cy.getByTestSubj('nav-item-id-ml:changePointDetections').should( + cy.getByTestSubj('nav-item-id-observability_project_nav.aiops').should( + 'have.class', + 'euiSideNavItemButton-isOpen' + ); + cy.getByTestSubj('nav-item-id-observability_project_nav.aiops.ml:changePointDetections').should( 'have.class', 'euiSideNavItemButton-isSelected' ); diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index 278da0f02d1d7..0b18ae0440e9b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -51,7 +51,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await expect(await browser.getCurrentUrl()).contain('/app/observability-log-explorer'); // check the aiops subsection - await svlCommonNavigation.sidenav.clickLink({ navId: 'aiops' }); // open ai ops subsection + await svlCommonNavigation.sidenav.clickLink({ navId: 'observability_project_nav.aiops' }); // open ai ops subsection await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'ml:anomalyDetection' }); await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'ml:anomalyDetection' }); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'AIOps' }); @@ -75,7 +75,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await expectNoPageReload(); }); - it('active sidenav section is auto opened on load', async () => { + // Skipping this test as it is not supported in the new navigation for now. + // Will be fixed in https://github.com/elastic/kibana/issues/167328 + it.skip('active sidenav section is auto opened on load', async () => { await svlCommonNavigation.sidenav.openSection('project_settings_project_nav'); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management' }); await browser.refresh(); From 08a07ce8429c4eb994e375b379820cc55d9ba573 Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Wed, 18 Oct 2023 11:51:54 +0200 Subject: [PATCH 25/49] do not add fleet server to settings in serverless in start_fleet_server script (#169200) ## Summary Starting fleet server fails when Kibana is running in serverless mode with `Fleet server host write APIs are disabled`. Not adding the fleet server to fleet settings fixes it. Kudos to @patrykkopycinski for the fix! --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../endpoint/common/fleet_server/fleet_server_services.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts index 4357adeeaf6cd..838e94d8faa00 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts @@ -287,7 +287,10 @@ const startFleetServerWithDocker = async ({ log.info(`Fleet server started`); - await addFleetServerHostToFleetSettings(kbnClient, log, fleetServerUrl); + if (!isServerless) { + await addFleetServerHostToFleetSettings(kbnClient, log, fleetServerUrl); + } + await updateFleetElasticsearchOutputHostNames(kbnClient, log); if (isServerless) { From 6d88fb5f54d4c1fbd8ff1b41d3bfcc4b3f244bed Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 18 Oct 2023 12:00:09 +0200 Subject: [PATCH 26/49] Add alerting_test_data package and a script to create rules for manual testing (#168493) Closes https://github.com/elastic/actionable-observability/issues/140 ## Summary Add alerting_test_data package to create custom threshold and APM rules for manual testing. More information about the usage: https://github.com/elastic/actionable-observability/pull/156 --------- Co-authored-by: almudenasanz Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + scripts/create_observability_rules.js | 16 ++++ tsconfig.base.json | 2 + .../alerting_test_data/README.md | 3 + .../observability/alerting_test_data/index.ts | 16 ++++ .../alerting_test_data/jest.config.js | 12 +++ .../alerting_test_data/kibana.jsonc | 5 ++ .../alerting_test_data/package.json | 8 ++ .../alerting_test_data/src/constants.ts | 19 ++++ .../create_apm_error_count_threshold_rule.ts | 66 ++++++++++++++ ...create_apm_failed_transaction_rate_rule.ts | 66 ++++++++++++++ .../src/create_custom_threshold_rule.ts | 89 +++++++++++++++++++ .../src/create_data_view.ts | 44 +++++++++ .../src/create_index_connector.ts | 30 +++++++ .../alerting_test_data/src/create_rule.ts | 21 +++++ .../alerting_test_data/src/get_kibana_url.ts | 59 ++++++++++++ .../alerting_test_data/src/run.ts | 51 +++++++++++ .../scenarios/custom_threshold_log_count.ts | 40 +++++++++ .../custom_threshold_log_count_groupby.ts | 41 +++++++++ .../custom_threshold_log_count_nodata.ts | 40 +++++++++ .../scenarios/custom_threshold_metric_avg.ts | 40 +++++++++ .../custom_threshold_metric_avg_groupby.ts | 41 +++++++++ .../custom_threshold_metric_avg_nodata.ts | 40 +++++++++ .../alerting_test_data/src/scenarios/index.ts | 13 +++ .../alerting_test_data/tsconfig.json | 22 +++++ yarn.lock | 4 + 27 files changed, 790 insertions(+) create mode 100644 scripts/create_observability_rules.js create mode 100644 x-pack/packages/observability/alerting_test_data/README.md create mode 100644 x-pack/packages/observability/alerting_test_data/index.ts create mode 100644 x-pack/packages/observability/alerting_test_data/jest.config.js create mode 100644 x-pack/packages/observability/alerting_test_data/kibana.jsonc create mode 100644 x-pack/packages/observability/alerting_test_data/package.json create mode 100644 x-pack/packages/observability/alerting_test_data/src/constants.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/create_apm_error_count_threshold_rule.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/create_apm_failed_transaction_rate_rule.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/create_data_view.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/create_index_connector.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/create_rule.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/get_kibana_url.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/run.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts create mode 100644 x-pack/packages/observability/alerting_test_data/src/scenarios/index.ts create mode 100644 x-pack/packages/observability/alerting_test_data/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 044ce6273292c..b07afe6135357 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -541,6 +541,7 @@ x-pack/plugins/notifications @elastic/appex-sharedux packages/kbn-object-versioning @elastic/appex-sharedux x-pack/plugins/observability_ai_assistant @elastic/obs-ai-assistant x-pack/packages/observability/alert_details @elastic/actionable-observability +x-pack/packages/observability/alerting_test_data @elastic/actionable-observability x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops x-pack/plugins/observability_log_explorer @elastic/infra-monitoring-ui x-pack/plugins/observability_onboarding @elastic/apm-ui diff --git a/package.json b/package.json index fcd74c483ebcd..5f17fe974cdb5 100644 --- a/package.json +++ b/package.json @@ -560,6 +560,7 @@ "@kbn/object-versioning": "link:packages/kbn-object-versioning", "@kbn/observability-ai-assistant-plugin": "link:x-pack/plugins/observability_ai_assistant", "@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details", + "@kbn/observability-alerting-test-data": "link:x-pack/packages/observability/alerting_test_data", "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", "@kbn/observability-log-explorer-plugin": "link:x-pack/plugins/observability_log_explorer", "@kbn/observability-onboarding-plugin": "link:x-pack/plugins/observability_onboarding", diff --git a/scripts/create_observability_rules.js b/scripts/create_observability_rules.js new file mode 100644 index 0000000000000..4dce916e6de07 --- /dev/null +++ b/scripts/create_observability_rules.js @@ -0,0 +1,16 @@ +/* + * 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. + */ + +require('@babel/register')({ + extensions: ['.ts', '.js'], + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], +}); + +var run = require('@kbn/observability-alerting-test-data').run; + +run(); diff --git a/tsconfig.base.json b/tsconfig.base.json index fd0601d9416fe..2da5e2023b35c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1076,6 +1076,8 @@ "@kbn/observability-ai-assistant-plugin/*": ["x-pack/plugins/observability_ai_assistant/*"], "@kbn/observability-alert-details": ["x-pack/packages/observability/alert_details"], "@kbn/observability-alert-details/*": ["x-pack/packages/observability/alert_details/*"], + "@kbn/observability-alerting-test-data": ["x-pack/packages/observability/alerting_test_data"], + "@kbn/observability-alerting-test-data/*": ["x-pack/packages/observability/alerting_test_data/*"], "@kbn/observability-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/observability"], "@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"], "@kbn/observability-log-explorer-plugin": ["x-pack/plugins/observability_log_explorer"], diff --git a/x-pack/packages/observability/alerting_test_data/README.md b/x-pack/packages/observability/alerting_test_data/README.md new file mode 100644 index 0000000000000..099040887958e --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/README.md @@ -0,0 +1,3 @@ +# @kbn/observability-alerting-test-data + +Provides utilities to generate alerting test data diff --git a/x-pack/packages/observability/alerting_test_data/index.ts b/x-pack/packages/observability/alerting_test_data/index.ts new file mode 100644 index 0000000000000..a7f0bd1e9e057 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { createApmErrorCountRule } from './src/create_apm_error_count_threshold_rule'; +export { createApmFailedTransactionRateRule } from './src/create_apm_failed_transaction_rate_rule'; +export { createCustomThresholdRule } from './src/create_custom_threshold_rule'; +export { createDataView } from './src/create_data_view'; +export { createIndexConnector } from './src/create_index_connector'; +export { createRule } from './src/create_rule'; +export { run } from './src/run'; + +export * from './src/scenarios'; diff --git a/x-pack/packages/observability/alerting_test_data/jest.config.js b/x-pack/packages/observability/alerting_test_data/jest.config.js new file mode 100644 index 0000000000000..05b0dbe613054 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/jest.config.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/observability/alerting_test_data'], +}; diff --git a/x-pack/packages/observability/alerting_test_data/kibana.jsonc b/x-pack/packages/observability/alerting_test_data/kibana.jsonc new file mode 100644 index 0000000000000..66b007cdd30b6 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/observability-alerting-test-data", + "owner": "@elastic/actionable-observability" +} diff --git a/x-pack/packages/observability/alerting_test_data/package.json b/x-pack/packages/observability/alerting_test_data/package.json new file mode 100644 index 0000000000000..f0023bc731ef9 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/observability-alerting-test-data", + "descriptio": "Utils to generate observability alerting test data", + "author": "Actionable Observability", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/packages/observability/alerting_test_data/src/constants.ts b/x-pack/packages/observability/alerting_test_data/src/constants.ts new file mode 100644 index 0000000000000..591f7f54191fc --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/constants.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const DATA_VIEW_ID = 'data-view-id'; +export const FIRED_ACTIONS_ID = 'custom_threshold.fired'; +export const ALERT_ACTION_INDEX = 'test-alert-action-index'; + +export const KIBANA_DEFAULT_URL = 'http://127.0.0.1:5601'; +export const USERNAME = 'elastic'; +export const PASSWORD = 'changeme'; + +export const HEADERS = { + 'kbn-xsrf': 'true', + 'x-elastic-internal-origin': 'foo', +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/create_apm_error_count_threshold_rule.ts b/x-pack/packages/observability/alerting_test_data/src/create_apm_error_count_threshold_rule.ts new file mode 100644 index 0000000000000..f98d8b1e23a99 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/create_apm_error_count_threshold_rule.ts @@ -0,0 +1,66 @@ +/* + * 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 { createRule } from './create_rule'; + +export const createApmErrorCountRule = async (actionId: string) => { + const apmErrorRateRuleParams = { + tags: ['apm'], + consumer: 'apm', + name: 'apm_error_count_threshold', + rule_type_id: 'apm.error_rate', + params: { + threshold: 5, + windowSize: 5, + windowUnit: 'm', + transactionType: undefined, + serviceName: undefined, + environment: 'ENVIRONMENT_ALL', + searchConfiguration: { + query: { + query: 'service.environment: "rule-test"', + language: 'kuery', + }, + }, + groupBy: ['service.name', 'service.environment'], + useKqlFilter: true, + }, + actions: [ + { + group: 'threshold_met', + id: actionId, + params: { + documents: [ + { + ruleName: '{{rule.name}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + environment: '{{context.environment}}', + interval: '{{context.interval}}', + reason: '{{context.reason}}', + serviceName: '{{context.serviceName}}', + threshold: '{{context.threshold}}', + transactionName: '{{context.transactionName}}', + transactionType: '{{context.transactionType}}', + triggerValue: '{{context.triggerValue}}', + viewInAppUrl: '{{context.viewInAppUrl}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + schedule: { + interval: '1m', + }, + }; + + return createRule(apmErrorRateRuleParams); +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/create_apm_failed_transaction_rate_rule.ts b/x-pack/packages/observability/alerting_test_data/src/create_apm_failed_transaction_rate_rule.ts new file mode 100644 index 0000000000000..204975a02cca6 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/create_apm_failed_transaction_rate_rule.ts @@ -0,0 +1,66 @@ +/* + * 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 { createRule } from './create_rule'; + +export const createApmFailedTransactionRateRule = async (actionId: string) => { + const apmErrorRateRuleParams = { + tags: ['apm'], + consumer: 'apm', + name: 'apm_failed_transaction_rate_threshold', + rule_type_id: 'apm.transaction_error_rate', + params: { + threshold: 30, + windowSize: 5, + windowUnit: 'm', + transactionType: undefined, + serviceName: undefined, + environment: 'ENVIRONMENT_ALL', + searchConfiguration: { + query: { + query: 'service.environment: "rule-test"', + language: 'kuery', + }, + }, + groupBy: ['service.name', 'service.environment', 'transaction.type'], + useKqlFilter: true, + }, + actions: [ + { + group: 'threshold_met', + id: actionId, + params: { + documents: [ + { + ruleName: '{{rule.name}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + environment: '{{context.environment}}', + interval: '{{context.interval}}', + reason: '{{context.reason}}', + serviceName: '{{context.serviceName}}', + threshold: '{{context.threshold}}', + transactionName: '{{context.transactionName}}', + transactionType: '{{context.transactionType}}', + triggerValue: '{{context.triggerValue}}', + viewInAppUrl: '{{context.viewInAppUrl}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + schedule: { + interval: '1m', + }, + }; + + return createRule(apmErrorRateRuleParams); +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts b/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts new file mode 100644 index 0000000000000..296d1b95512d3 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { FIRED_ACTIONS_ID } from './constants'; +import { createRule } from './create_rule'; + +export const createCustomThresholdRule = async ( + actionId: string, + dataViewId: string, + ruleParams: { + consumer?: string; + name?: string; + params?: { + criteria: any[]; + groupBy?: string[]; + searchConfiguration: { + query: { + query?: string; + }; + }; + }; + } +) => { + const customThresholdRuleParams = { + tags: ['observability'], + consumer: ruleParams.consumer || 'logs', + name: ruleParams.name || 'Default custom threshold rule name', + rule_type_id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: ruleParams.params?.criteria || [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [1], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + groupBy: ruleParams.params?.groupBy, + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: ruleParams.params?.searchConfiguration.query.query || '', + language: 'kuery', + }, + index: dataViewId, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleName: '{{rule.name}}', + ruleType: '{{rule.type}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + reason: '{{context.reason}}', + value: '{{context.value}}', + host: '{{context.host}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + schedule: { + interval: '1m', + }, + }; + + return createRule(customThresholdRuleParams); +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/create_data_view.ts b/x-pack/packages/observability/alerting_test_data/src/create_data_view.ts new file mode 100644 index 0000000000000..13e966c8c1759 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/create_data_view.ts @@ -0,0 +1,44 @@ +/* + * 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 axios from 'axios'; +import { HEADERS, PASSWORD, USERNAME } from './constants'; +import { getKibanaUrl } from './get_kibana_url'; + +export const createDataView = async ({ + indexPattern, + id, +}: { + indexPattern: string; + id: string; +}) => { + const DATA_VIEW_CREATION_API = `${await getKibanaUrl()}/api/content_management/rpc/create`; + const dataViewParams = { + contentTypeId: 'index-pattern', + data: { + fieldAttrs: '{}', + title: indexPattern, + timeFieldName: '@timestamp', + sourceFilters: '[]', + fields: '[]', + fieldFormatMap: '{}', + typeMeta: '{}', + runtimeFieldMap: '{}', + name: indexPattern, + }, + options: { id }, + version: 1, + }; + + return axios.post(DATA_VIEW_CREATION_API, dataViewParams, { + headers: HEADERS, + auth: { + username: USERNAME, + password: PASSWORD, + }, + }); +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/create_index_connector.ts b/x-pack/packages/observability/alerting_test_data/src/create_index_connector.ts new file mode 100644 index 0000000000000..19fe799bae2f6 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/create_index_connector.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; +import { ALERT_ACTION_INDEX, HEADERS, PASSWORD, USERNAME } from './constants'; +import { getKibanaUrl } from './get_kibana_url'; + +export const createIndexConnector = async () => { + const INDEX_CONNECTOR_API = `${await getKibanaUrl()}/api/actions/connector`; + const indexConnectorParams = { + name: 'Test Index Connector', + config: { + index: ALERT_ACTION_INDEX, + refresh: true, + }, + connector_type_id: '.index', + }; + + return axios.post(INDEX_CONNECTOR_API, indexConnectorParams, { + headers: HEADERS, + auth: { + username: USERNAME, + password: PASSWORD, + }, + }); +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/create_rule.ts b/x-pack/packages/observability/alerting_test_data/src/create_rule.ts new file mode 100644 index 0000000000000..3b5fcb92562dc --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/create_rule.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 axios from 'axios'; +import { HEADERS, PASSWORD, USERNAME } from './constants'; +import { getKibanaUrl } from './get_kibana_url'; + +export const createRule = async (ruleParams: any) => { + const RULE_CREATION_API = `${await getKibanaUrl()}/api/alerting/rule`; + return axios.post(RULE_CREATION_API, ruleParams, { + headers: HEADERS, + auth: { + username: USERNAME, + password: PASSWORD, + }, + }); +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/get_kibana_url.ts b/x-pack/packages/observability/alerting_test_data/src/get_kibana_url.ts new file mode 100644 index 0000000000000..5e6062310f49e --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/get_kibana_url.ts @@ -0,0 +1,59 @@ +/* + * 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 fetch from 'node-fetch'; +import { format, parse } from 'url'; +import { KIBANA_DEFAULT_URL } from './constants'; + +let kibanaUrl: string; + +export async function getKibanaUrl() { + if (kibanaUrl) { + return kibanaUrl; + } + + try { + const unredirectedResponse = await fetch(KIBANA_DEFAULT_URL, { + method: 'HEAD', + follow: 1, + redirect: 'manual', + }); + + const discoveredKibanaUrl = + unredirectedResponse.headers + .get('location') + ?.replace('/spaces/enter', '') + ?.replace('spaces/space_selector', '') || KIBANA_DEFAULT_URL; + + const parsedTarget = parse(KIBANA_DEFAULT_URL); + + const parsedDiscoveredUrl = parse(discoveredKibanaUrl); + + const discoveredKibanaUrlWithAuth = format({ + ...parsedDiscoveredUrl, + auth: parsedTarget.auth, + }); + + const redirectedResponse = await fetch(discoveredKibanaUrlWithAuth, { + method: 'HEAD', + }); + + if (redirectedResponse.status !== 200) { + throw new Error( + `Expected HTTP 200 from ${discoveredKibanaUrlWithAuth}, got ${redirectedResponse.status}` + ); + } + + // eslint-disable-next-line no-console + console.log(`Discovered kibana running at: ${discoveredKibanaUrlWithAuth}`); + + kibanaUrl = discoveredKibanaUrlWithAuth.replace(/\/$/, ''); + return kibanaUrl; + } catch (error) { + throw new Error(`Could not connect to Kibana: ` + error.message); + } +} diff --git a/x-pack/packages/observability/alerting_test_data/src/run.ts b/x-pack/packages/observability/alerting_test_data/src/run.ts new file mode 100644 index 0000000000000..63c0c613e3e90 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/run.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createApmErrorCountRule } from './create_apm_error_count_threshold_rule'; +import { createApmFailedTransactionRateRule } from './create_apm_failed_transaction_rate_rule'; +import { createCustomThresholdRule } from './create_custom_threshold_rule'; +import { createDataView } from './create_data_view'; +import { createIndexConnector } from './create_index_connector'; + +import { scenario1, scenario2, scenario3, scenario4, scenario5, scenario6 } from './scenarios'; + +const scenarios = [ + // Logs use-cases + scenario1, + scenario2, + scenario3, + // Metrics use-cases + scenario4, + scenario5, + scenario6, +]; + +/* eslint-disable no-console */ +export async function run() { + console.log('Creating index connector - start'); + const response = await createIndexConnector(); + const actionId = await response.data.id; + console.log('Creating index connector - finished - actionId: ', actionId); + for (const scenario of scenarios) { + if (scenario.dataView.shouldCreate) { + console.log('Creating data view - start - id: ', scenario.dataView.id); + await createDataView(scenario.dataView); + console.log('Creating data view - finished - id: ', scenario.dataView.id); + } + console.log('Creating Custom threshold rule - start - name: ', scenario.ruleParams.name); + await createCustomThresholdRule(actionId, scenario.dataView.id, scenario.ruleParams); + console.log('Creating Custom threshold rule - finished - name: ', scenario.ruleParams.name); + } + + console.log('Creating APM error count rule - start'); + await createApmErrorCountRule(actionId); + console.log('Creating APM error count rule - finished'); + + console.log('Creating APM failed transaction rate rule - start'); + await createApmFailedTransactionRateRule(actionId); + console.log('Creating APM failed transaction rate rule - finished'); +} diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts new file mode 100644 index 0000000000000..39b7159ff478b --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; + +export const scenario1 = { + dataView: { + indexPattern: 'high-cardinality-data-fake_hosts.fake_hosts-*', + id: 'data-view-id', + shouldCreate: true, + }, + ruleParams: { + consumer: 'logs', + name: 'custom_threshold_log_count', + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.LT, + threshold: [100], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + searchConfiguration: { + query: { + query: 'labels.scenario: custom_threshold_log_count', + }, + }, + }, + }, +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts new file mode 100644 index 0000000000000..882d83bbbf973 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts @@ -0,0 +1,41 @@ +/* + * 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 { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; + +export const scenario2 = { + dataView: { + indexPattern: 'high-cardinality-data-fake_hosts.fake_hosts-*', + id: 'data-view-id', + shouldCreate: false, + }, + ruleParams: { + consumer: 'logs', + name: 'custom_threshold_log_count_groupby', + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.LT, + threshold: [40], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + groupBy: ['event.dataset'], + searchConfiguration: { + query: { + query: 'labels.scenario: custom_threshold_log_count_groupby', + }, + }, + }, + }, +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts new file mode 100644 index 0000000000000..7be8b142380d1 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; + +export const scenario3 = { + dataView: { + indexPattern: 'high-cardinality-data-fake_hosts.fake_hosts-*', + id: 'data-view-id', + shouldCreate: false, + }, + ruleParams: { + consumer: 'logs', + name: 'custom_threshold_log_count_nodata', + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.LT, + threshold: [5], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + searchConfiguration: { + query: { + query: 'labels.scenario: custom_threshold_log_count_nodata', + }, + }, + }, + }, +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts new file mode 100644 index 0000000000000..755f89680b171 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; + +export const scenario4 = { + dataView: { + indexPattern: 'high-cardinality-data-fake_hosts.fake_hosts-*', + id: 'data-view-id', + shouldCreate: false, + }, + ruleParams: { + consumer: 'logs', + name: 'custom_threshold_metric_avg', + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [80], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }], + }, + ], + searchConfiguration: { + query: { + query: 'labels.scenario: custom_threshold_metric_avg', + }, + }, + }, + }, +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts new file mode 100644 index 0000000000000..a662608ab31d6 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts @@ -0,0 +1,41 @@ +/* + * 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 { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; + +export const scenario5 = { + dataView: { + indexPattern: 'high-cardinality-data-fake_hosts.fake_hosts-*', + id: 'data-view-id', + shouldCreate: false, + }, + ruleParams: { + consumer: 'logs', + name: 'custom_threshold_metric_avg_groupby', + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [80], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }], + }, + ], + groupBy: ['host.name'], + searchConfiguration: { + query: { + query: 'labels.scenario: custom_threshold_metric_avg_groupby', + }, + }, + }, + }, +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts new file mode 100644 index 0000000000000..5e98aa1a58d31 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; + +export const scenario6 = { + dataView: { + indexPattern: 'high-cardinality-data-fake_hosts.fake_hosts-*', + id: 'data-view-id', + shouldCreate: false, + }, + ruleParams: { + consumer: 'logs', + name: 'custom_threshold_metric_avg_nodata', + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.LT, + threshold: [1], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }], + }, + ], + searchConfiguration: { + query: { + query: 'labels.scenario: custom_threshold_metric_avg_nodata', + }, + }, + }, + }, +}; diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/index.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/index.ts new file mode 100644 index 0000000000000..c91fbcfac4ca6 --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { scenario1 } from './custom_threshold_log_count'; +export { scenario2 } from './custom_threshold_log_count_groupby'; +export { scenario3 } from './custom_threshold_log_count_nodata'; +export { scenario4 } from './custom_threshold_metric_avg'; +export { scenario5 } from './custom_threshold_metric_avg_groupby'; +export { scenario6 } from './custom_threshold_metric_avg_nodata'; diff --git a/x-pack/packages/observability/alerting_test_data/tsconfig.json b/x-pack/packages/observability/alerting_test_data/tsconfig.json new file mode 100644 index 0000000000000..d2a9a55a887de --- /dev/null +++ b/x-pack/packages/observability/alerting_test_data/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/observability-plugin", + "@kbn/rule-data-utils", + ] +} diff --git a/yarn.lock b/yarn.lock index bf30fbda1764d..f61f8f0f14da5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5090,6 +5090,10 @@ version "0.0.0" uid "" +"@kbn/observability-alerting-test-data@link:x-pack/packages/observability/alerting_test_data": + version "0.0.0" + uid "" + "@kbn/observability-fixtures-plugin@link:x-pack/test/cases_api_integration/common/plugins/observability": version "0.0.0" uid "" From 9a1ae4b03aeb84337f92a144aac08cc7cc64af2a Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 18 Oct 2023 12:20:00 +0200 Subject: [PATCH 27/49] [Security Solution][Serverless] Unified Management nav (#168946) ## Summary needed for: https://github.com/elastic/kibana/issues/166545 This PR makes the generic Management landing page available in Security Solution project navigation. Unifying the experience with the rest of Solutions - Security Solution specific `Project Settings` landing page removed. - `SecurityPageName.projectSettings` removed. - `Project Settings` landing page component and route were removed. - Unused icons cleaned. - Link to Management application added in the SideNav. - Management app "cards landing page" enabled. - Cleaned the redirect logic in the Management app, this API was created only for Security and is not used anymore. - `developerSettings` config is not needed anymore and has been removed. - `Entity risk score`, `Maps`, and `Visualize library` links are configured and available, but they are not displayed in the side navigation. They will be added as cards on the Management landing page in a follow-up. - Unified Navigation design implemented as a backup plan for Serverless public preview. - Shared-ux `DefaultNavigation` implementation is still under the `platformNavEnabled` experimental flag. ## Screenshot screenshot --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/management/README.md | 10 - .../public/components/landing/landing.tsx | 17 +- .../management_app/management_app.tsx | 8 +- src/plugins/management/public/mocks/index.ts | 1 - src/plugins/management/public/plugin.tsx | 4 - src/plugins/management/public/types.ts | 6 - src/plugins/management/tsconfig.json | 2 - .../navigation/src/constants.ts | 1 - .../common/config.ts | 20 -- .../public/common/icons/index_management.tsx | 68 ------ .../public/common/icons/map_services.tsx | 33 --- .../public/common/icons/reporting.tsx | 37 ---- .../public/common/icons/users_roles.tsx | 57 ----- .../public/common/lazy_icons.tsx | 9 +- .../public/common/services/create_services.ts | 7 +- .../welcome_panel/change_plan_link.tsx | 2 - .../public/navigation/index.ts | 27 +-- .../public/navigation/links/app_links.ts | 58 +++--- .../public/navigation/links/constants.ts | 3 +- .../public/navigation/links/nav.links.test.ts | 194 ++++++------------ .../public/navigation/links/nav_links.ts | 38 +--- .../links/sections/dev_tools_links.ts | 1 + .../links/sections/dev_tools_translations.ts | 2 +- .../links/sections/project_settings_links.ts | 182 ++-------------- .../sections/project_settings_translations.ts | 169 +-------------- .../navigation/navigation_tree/utils.ts | 4 +- .../side_navigation/side_navigation.test.tsx | 62 +++++- .../side_navigation/side_navigation.tsx | 102 ++++++--- .../side_navigation_footer.test.tsx | 125 +++++++++++ .../side_navigation_footer.tsx | 153 ++++++++++++++ .../navigation/side_navigation/types.ts | 11 + .../use_side_nav_items.test.tsx | 3 +- .../side_navigation/use_side_nav_items.ts | 51 ++--- .../public/pages/project_settings.tsx | 61 ------ .../public/pages/routes.tsx | 7 - .../public/plugin.ts | 4 +- .../public/types.ts | 2 +- .../server/config.ts | 4 +- .../tsconfig.json | 1 - .../test_suites/security/ftr/management.ts | 34 --- .../functional/test_suites/security/index.ts | 1 - 41 files changed, 584 insertions(+), 997 deletions(-) delete mode 100644 x-pack/plugins/security_solution_serverless/public/common/icons/index_management.tsx delete mode 100644 x-pack/plugins/security_solution_serverless/public/common/icons/map_services.tsx delete mode 100644 x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx delete mode 100644 x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx create mode 100644 x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.test.tsx create mode 100644 x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.tsx create mode 100644 x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/types.ts delete mode 100644 x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx delete mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/management.ts diff --git a/src/plugins/management/README.md b/src/plugins/management/README.md index 828715235b1b9..15974f6d4814d 100644 --- a/src/plugins/management/README.md +++ b/src/plugins/management/README.md @@ -39,13 +39,3 @@ If card needs to be hidden from the navigation you can specify that by using the ``` More specifics about the `setupCardsNavigation` can be found in `packages/kbn-management/cards_navigation/readme.mdx`. - -## Landing page redirect - -If the consumer wants to have a separate landing page for the management section, they can use the `setLandingPageRedirect` -method to specify the path to the landing page: - - -``` - management.setLandingPageRedirect('/app/security/management'); -``` \ No newline at end of file diff --git a/src/plugins/management/public/components/landing/landing.tsx b/src/plugins/management/public/components/landing/landing.tsx index b7d48d7b08491..90323ad838eb9 100644 --- a/src/plugins/management/public/components/landing/landing.tsx +++ b/src/plugins/management/public/components/landing/landing.tsx @@ -24,24 +24,9 @@ export const ManagementLandingPage = ({ setBreadcrumbs, onAppMounted, }: ManagementLandingPageProps) => { - const { - appBasePath, - sections, - kibanaVersion, - cardsNavigationConfig, - landingPageRedirect, - navigateToUrl, - basePath, - } = useAppContext(); + const { appBasePath, sections, kibanaVersion, cardsNavigationConfig } = useAppContext(); setBreadcrumbs(); - // Redirect the user to the configured landing page if there is one - useEffect(() => { - if (landingPageRedirect) { - navigateToUrl(basePath.prepend(landingPageRedirect)); - } - }, [landingPageRedirect, navigateToUrl, basePath]); - useEffect(() => { onAppMounted(''); }, [onAppMounted]); diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx index 0781e433b85bb..dc6a67a406282 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -43,7 +43,6 @@ export interface ManagementAppDependencies { setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; isSidebarEnabled$: BehaviorSubject; cardsNavigationConfig$: BehaviorSubject; - landingPageRedirect$: BehaviorSubject; } export const ManagementApp = ({ @@ -52,13 +51,11 @@ export const ManagementApp = ({ theme$, appBasePath, }: ManagementAppProps) => { - const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$, landingPageRedirect$ } = - dependencies; + const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies; const [selectedId, setSelectedId] = useState(''); const [sections, setSections] = useState(); const isSidebarEnabled = useObservable(isSidebarEnabled$); const cardsNavigationConfig = useObservable(cardsNavigationConfig$); - const landingPageRedirect = useObservable(landingPageRedirect$); const onAppMounted = useCallback((id: string) => { setSelectedId(id); @@ -114,9 +111,6 @@ export const ManagementApp = ({ sections, cardsNavigationConfig, kibanaVersion: dependencies.kibanaVersion, - landingPageRedirect, - navigateToUrl: dependencies.coreStart.application.navigateToUrl, - basePath: dependencies.coreStart.http.basePath, }; return ( diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index bc6bdcdc46814..93ccefbe5130f 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -44,7 +44,6 @@ const createSetupContract = (): ManagementSetup => ({ const createStartContract = (): ManagementStart => ({ setIsSidebarEnabled: jest.fn(), setupCardsNavigation: jest.fn(), - setLandingPageRedirect: jest.fn(), }); export const managementPluginMock = { diff --git a/src/plugins/management/public/plugin.tsx b/src/plugins/management/public/plugin.tsx index cf0ec90af6682..7b6b0c06ec731 100644 --- a/src/plugins/management/public/plugin.tsx +++ b/src/plugins/management/public/plugin.tsx @@ -90,7 +90,6 @@ export class ManagementPlugin private hasAnyEnabledApps = true; private isSidebarEnabled$ = new BehaviorSubject(true); - private landingPageRedirect$ = new BehaviorSubject(undefined); private cardsNavigationConfig$ = new BehaviorSubject({ enabled: false, hideLinksTo: [], @@ -151,7 +150,6 @@ export class ManagementPlugin }, isSidebarEnabled$: managementPlugin.isSidebarEnabled$, cardsNavigationConfig$: managementPlugin.cardsNavigationConfig$, - landingPageRedirect$: managementPlugin.landingPageRedirect$, }); }, }); @@ -209,8 +207,6 @@ export class ManagementPlugin this.isSidebarEnabled$.next(isSidebarEnabled), setupCardsNavigation: ({ enabled, hideLinksTo }) => this.cardsNavigationConfig$.next({ enabled, hideLinksTo }), - setLandingPageRedirect: (landingPageRedirect: string) => - this.landingPageRedirect$.next(landingPageRedirect), }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 31755c39cc7c2..439f827797f6b 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -12,8 +12,6 @@ import type { LocatorPublic } from '@kbn/share-plugin/common'; import { ChromeBreadcrumb, CoreTheme } from '@kbn/core/public'; import type { AppId } from '@kbn/management-cards-navigation'; import { AppNavLinkStatus } from '@kbn/core/public'; -import type { ApplicationStart } from '@kbn/core-application-browser'; -import type { HttpStart } from '@kbn/core-http-browser'; import { ManagementSection, RegisterManagementSectionArgs } from './utils'; import type { ManagementAppLocatorParams } from '../common/locator'; @@ -33,7 +31,6 @@ export interface DefinedSections { export interface ManagementStart { setIsSidebarEnabled: (enabled: boolean) => void; - setLandingPageRedirect: (landingPageRedirect: string) => void; setupCardsNavigation: ({ enabled, hideLinksTo }: NavigationCardsSubject) => void; } @@ -95,9 +92,6 @@ export interface AppDependencies { kibanaVersion: string; sections: ManagementSection[]; cardsNavigationConfig?: NavigationCardsSubject; - landingPageRedirect: string | undefined; - navigateToUrl: ApplicationStart['navigateToUrl']; - basePath: HttpStart['basePath']; } export interface ConfigSchema { diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index 0f060475796c5..493178682496b 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -24,8 +24,6 @@ "@kbn/shared-ux-link-redirect-app", "@kbn/test-jest-helpers", "@kbn/config-schema", - "@kbn/core-application-browser", - "@kbn/core-http-browser", "@kbn/serverless", "@kbn/management-settings-application", "@kbn/react-kibana-context-render", diff --git a/x-pack/packages/security-solution/navigation/src/constants.ts b/x-pack/packages/security-solution/navigation/src/constants.ts index 8698a5dc8ac2a..704c24424cd43 100644 --- a/x-pack/packages/security-solution/navigation/src/constants.ts +++ b/x-pack/packages/security-solution/navigation/src/constants.ts @@ -59,7 +59,6 @@ export enum SecurityPageName { noPage = '', overview = 'overview', policies = 'policy', - projectSettings = 'project_settings', responseActionsHistory = 'response_actions_history', rules = 'rules', rulesAdd = 'rules-add', diff --git a/x-pack/plugins/security_solution_serverless/common/config.ts b/x-pack/plugins/security_solution_serverless/common/config.ts index b1aaef412fcb1..f4e9151988a60 100644 --- a/x-pack/plugins/security_solution_serverless/common/config.ts +++ b/x-pack/plugins/security_solution_serverless/common/config.ts @@ -33,28 +33,8 @@ export const productTypes = schema.arrayOf(productType, { }); export type SecurityProductTypes = TypeOf; -/** - * Developer only options that can be set in `serverless.security.dev.yml` - */ -export const developerConfigSchema = schema.object({ - /** - * Disables the redirect in the UI for kibana management pages (ex. users, roles, etc). - * - * NOTE: you likely will also need to add the following to your `serverless.security.dev.yml` - * file if wanting to access the user, roles and role mapping pages via URL - * - * xpack.security.ui.userManagementEnabled: true - * xpack.security.ui.roleManagementEnabled: true - * xpack.security.ui.roleMappingManagementEnabled: true - */ - disableManagementUrlRedirect: schema.boolean({ defaultValue: false }), -}); - -export type DeveloperConfig = TypeOf; - export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), - developer: developerConfigSchema, productTypes, /** * For internal use. A list of string values (comma delimited) that will enable experimental diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/index_management.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/index_management.tsx deleted file mode 100644 index 8684c5bd05279..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/index_management.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconIndexManagement: React.FC> = ({ ...props }) => ( - - - - - - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconIndexManagement; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/map_services.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/map_services.tsx deleted file mode 100644 index a9004b486228c..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/map_services.tsx +++ /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 type { SVGProps } from 'react'; -import React from 'react'; -export const IconMapServices: React.FC> = ({ ...props }) => ( - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconMapServices; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx deleted file mode 100644 index ba783401ef7e5..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/reporting.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconReporting: React.FC> = ({ ...props }) => ( - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconReporting; diff --git a/x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx b/x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx deleted file mode 100644 index 3eb961f783f67..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/common/icons/users_roles.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { SVGProps } from 'react'; -import React from 'react'; -export const IconUsersRoles: React.FC> = ({ ...props }) => ( - - - - - - - -); - -// eslint-disable-next-line import/no-default-export -export default IconUsersRoles; diff --git a/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx b/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx index f7e016d75c969..c49c4695b1493 100644 --- a/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx +++ b/x-pack/plugins/security_solution_serverless/public/common/lazy_icons.tsx @@ -19,18 +19,11 @@ const withSuspenseIcon = (Component: React.ComponentType< export const IconLensLazy = withSuspenseIcon(React.lazy(() => import('./icons/lens'))); export const IconEndpointLazy = withSuspenseIcon(React.lazy(() => import('./icons/endpoint'))); -export const IconIndexManagementLazy = withSuspenseIcon( - React.lazy(() => import('./icons/index_management')) -); export const IconFleetLazy = withSuspenseIcon(React.lazy(() => import('./icons/fleet'))); export const IconEcctlLazy = withSuspenseIcon(React.lazy(() => import('./icons/ecctl'))); -export const IconMapServicesLazy = withSuspenseIcon( - React.lazy(() => import('./icons/map_services')) -); + export const IconTimelineLazy = withSuspenseIcon(React.lazy(() => import('./icons/timeline'))); export const IconOsqueryLazy = withSuspenseIcon(React.lazy(() => import('./icons/osquery'))); -export const IconUsersRolesLazy = withSuspenseIcon(React.lazy(() => import('./icons/users_roles'))); -export const IconReportingLazy = withSuspenseIcon(React.lazy(() => import('./icons/reporting'))); export const IconVisualizationLazy = withSuspenseIcon( React.lazy(() => import('./icons/visualization')) ); diff --git a/x-pack/plugins/security_solution_serverless/public/common/services/create_services.ts b/x-pack/plugins/security_solution_serverless/public/common/services/create_services.ts index 7967f8d00d6fb..790f65f2af819 100644 --- a/x-pack/plugins/security_solution_serverless/public/common/services/create_services.ts +++ b/x-pack/plugins/security_solution_serverless/public/common/services/create_services.ts @@ -21,12 +21,7 @@ export const createServices = ( experimentalFeatures: ExperimentalFeatures ): Services => { const { securitySolution, cloud } = pluginsStart; - const projectNavLinks$ = createProjectNavLinks$( - securitySolution.getNavLinks$(), - core, - cloud, - experimentalFeatures - ); + const projectNavLinks$ = createProjectNavLinks$(securitySolution.getNavLinks$(), core, cloud); return { ...core, ...pluginsStart, diff --git a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx index 19d6afd4a5843..b41dc0829f62b 100644 --- a/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx +++ b/x-pack/plugins/security_solution_serverless/public/get_started/welcome_panel/change_plan_link.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import { SecurityPageName } from '@kbn/security-solution-plugin/common'; import { EuiFlexGroup, EuiFlexItem, @@ -43,7 +42,6 @@ const ChangePlanLinkComponent = ({ productTier }: { productTier: ProductTier | u { - securitySolution.setAppLinksSwitcher(getProjectAppLinksSwitcher(experimentalFeatures)); + securitySolution.setAppLinksSwitcher(projectAppLinksSwitcher); securitySolution.setDeepLinksFormatter(formatProjectDeepLinks); }; -export const startNavigation = (services: Services, config: ServerlessSecurityPublicConfig) => { +export const startNavigation = (services: Services) => { const { serverless, management } = services; serverless.setProjectHome(APP_PATH); @@ -45,9 +35,8 @@ export const startNavigation = (services: Services, config: ServerlessSecurityPu serverless.setSideNavComponent(getDefaultNavigationComponent(navigationTree, services)); }); } else { - if (!config.developer.disableManagementUrlRedirect) { - management.setLandingPageRedirect(SECURITY_PROJECT_SETTINGS_PATH); - } + management.setupCardsNavigation({ enabled: true }); + projectNavigationTree.getChromeNavigationTree$().subscribe((chromeNavigationTree) => { serverless.setNavigation({ navigationTree: chromeNavigationTree }); }); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/app_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/app_links.ts index 4cb0cc4a7fd7c..9d591491cede6 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/app_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/app_links.ts @@ -14,39 +14,33 @@ import { cloneDeep, remove } from 'lodash'; import { createInvestigationsLinkFromTimeline } from './sections/investigations_links'; import { mlAppLink } from './sections/ml_links'; import { createAssetsLinkFromManage } from './sections/assets_links'; -import { createProjectSettingsLinkFromManage } from './sections/project_settings_links'; -import type { ExperimentalFeatures } from '../../../common/experimental_features'; +import { createProjectSettingsLinksFromManage } from './sections/project_settings_links'; // This function is called by the security_solution plugin to alter the app links // that will be registered to the Security Solution application on Serverless projects. // The capabilities filtering is done after this function is called by the security_solution plugin. -export const getProjectAppLinksSwitcher = - (experimentalFeatures: ExperimentalFeatures): AppLinksSwitcher => - (appLinks) => { - const projectAppLinks = cloneDeep(appLinks) as LinkItem[]; - - // Remove timeline link - const [timelineLinkItem] = remove(projectAppLinks, { id: SecurityPageName.timelines }); - if (timelineLinkItem) { - // Add investigations link - projectAppLinks.push(createInvestigationsLinkFromTimeline(timelineLinkItem)); - } - - // Remove manage link - const [manageLinkItem] = remove(projectAppLinks, { id: SecurityPageName.administration }); - - if (manageLinkItem) { - // Add assets link - projectAppLinks.push(createAssetsLinkFromManage(manageLinkItem)); - } - - // Add ML link - projectAppLinks.push(mlAppLink); - - if (!experimentalFeatures.platformNavEnabled && manageLinkItem) { - // Add project settings link - projectAppLinks.push(createProjectSettingsLinkFromManage(manageLinkItem)); - } - - return projectAppLinks; - }; +export const projectAppLinksSwitcher: AppLinksSwitcher = (appLinks) => { + const projectAppLinks = cloneDeep(appLinks) as LinkItem[]; + + // Remove timeline link + const [timelineLinkItem] = remove(projectAppLinks, { id: SecurityPageName.timelines }); + if (timelineLinkItem) { + // Add investigations link + projectAppLinks.push(createInvestigationsLinkFromTimeline(timelineLinkItem)); + } + + // Remove manage link + const [manageLinkItem] = remove(projectAppLinks, { id: SecurityPageName.administration }); + + if (manageLinkItem) { + // Add assets link + projectAppLinks.push(createAssetsLinkFromManage(manageLinkItem)); + // Add entity analytics link if exists + projectAppLinks.push(...createProjectSettingsLinksFromManage(manageLinkItem)); + } + + // Add ML link + projectAppLinks.push(mlAppLink); + + return projectAppLinks; +}; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts index dca89a4a00111..57f333f90ffc3 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/constants.ts @@ -13,7 +13,6 @@ export const SecurityPagePath = { [SecurityPageName.mlLanding]: '/ml', [SecurityPageName.assets]: '/assets', [SecurityPageName.cloudDefend]: '/cloud_defend', - [SecurityPageName.projectSettings]: '/project_settings', } as const; /** @@ -74,6 +73,7 @@ export enum ExternalPageName { integrationsSecurity = 'integrations:/browse/security', // Management // Ref: packages/default-nav/management/default_navigation.ts + management = 'management:', managementIngestPipelines = 'management:ingest_pipelines', managementPipelines = 'management:pipelines', managementIndexManagement = 'management:index_management', @@ -97,4 +97,5 @@ export enum ExternalPageName { // cloudUrlKey Ref: x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts cloudUsersAndRoles = 'cloud:usersAndRoles', cloudBilling = 'cloud:billing', + cloudPerformance = 'cloud:performance', } diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/nav.links.test.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/nav.links.test.ts index d06b28df01955..2fb039dbc52bf 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/nav.links.test.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/nav.links.test.ts @@ -9,19 +9,14 @@ import { APP_UI_ID } from '@kbn/security-solution-plugin/common'; import type { NavigationLink } from '@kbn/security-solution-navigation'; import { SecurityPageName } from '@kbn/security-solution-navigation'; import { createProjectNavLinks$ } from './nav_links'; +import type { Observable } from 'rxjs'; import { BehaviorSubject, firstValueFrom, take } from 'rxjs'; import { mockServices } from '../../common/services/__mocks__/services.mock'; import { mlNavCategories, mlNavLinks } from './sections/ml_links'; import { assetsNavLinks } from './sections/assets_links'; import { ExternalPageName } from './constants'; -import type { ProjectNavigationLink } from './types'; import { investigationsNavLinks } from './sections/investigations_links'; -import { - projectSettingsNavCategories, - projectSettingsNavLinks, -} from './sections/project_settings_links'; import { isCloudLink } from './util'; -import type { ExperimentalFeatures } from '../../../common/experimental_features'; const mockCloudStart = mockServices.cloud; const mockChromeNavLinks = jest.fn((): ChromeNavLink[] => []); @@ -40,7 +35,6 @@ const testServices = { }, }, }; -const experimentalFeatures = { platformNavEnabled: false } as ExperimentalFeatures; const link1Id = 'link-1' as SecurityPageName; const link2Id = 'link-2' as SecurityPageName; @@ -52,15 +46,6 @@ const linkMlLanding: NavigationLink = { title: 'ML Landing', links: [], }; -const projectLinkDevTools: ProjectNavigationLink = { - id: ExternalPageName.devTools, - title: 'Dev tools', -}; - -const projectLinkDiscover: ProjectNavigationLink = { - id: ExternalPageName.discover, - title: 'Discover', -}; const chromeNavLink1: ChromeNavLink = { id: `${APP_UI_ID}:${link1.id}`, @@ -77,6 +62,22 @@ const devToolsChromeNavLink: ChromeNavLink = { baseUrl: '', }; +const createTestProjectNavLinks = async ( + testSecurityNavLinks$: Observable>>, + { filterCloudLinks = true }: { filterCloudLinks?: boolean } = {} +) => { + const projectNavLinks$ = createProjectNavLinks$( + testSecurityNavLinks$, + testServices, + mockCloudStart + ); + const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); + if (filterCloudLinks) { + return value.filter((link) => !isCloudLink(link.id)); + } + return value; +}; + describe('getProjectNavLinks', () => { beforeEach(() => { jest.clearAllMocks(); @@ -86,47 +87,19 @@ describe('getProjectNavLinks', () => { ); }); - it('should return security nav links with all external links filtered', async () => { + it('should return security nav links with all external (non cloud) links filtered', async () => { mockChromeNavLinksHas.mockReturnValue(false); // no external links exist const testSecurityNavLinks$ = new BehaviorSubject([link1, link2]); - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures - ); - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); + const value = await createTestProjectNavLinks(testSecurityNavLinks$); expect(value).toEqual([link1, link2]); }); - it('should add devTools nav link if chrome nav link exists', async () => { - mockChromeNavLinks.mockReturnValue([devToolsChromeNavLink]); - const testSecurityNavLinks$ = new BehaviorSubject([link1]); - - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures - ); - - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); - expect(value).toEqual([link1, projectLinkDevTools]); - }); - it('should filter all external links not configured in chrome links', async () => { mockChromeNavLinks.mockReturnValue([chromeNavLink1]); const testSecurityNavLinks$ = new BehaviorSubject([link1, link2, linkMlLanding]); - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures - ); - - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); + const value = await createTestProjectNavLinks(testSecurityNavLinks$); expect(value).toEqual([ link1, link2, @@ -134,25 +107,26 @@ describe('getProjectNavLinks', () => { ]); }); + it('should add devTools nav link if chrome nav link exists', async () => { + mockChromeNavLinks.mockReturnValue([devToolsChromeNavLink]); + const testSecurityNavLinks$ = new BehaviorSubject([link1]); + + const value = await createTestProjectNavLinks(testSecurityNavLinks$); + expect(value).toEqual([link1, expect.objectContaining({ id: ExternalPageName.devTools })]); + }); + it('should add machineLearning links', async () => { mockChromeNavLinksHas.mockReturnValue(true); // all links exist const testSecurityNavLinks$ = new BehaviorSubject([link1, link2, linkMlLanding]); - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures + const value = await createTestProjectNavLinks(testSecurityNavLinks$); + expect(value).toEqual( + expect.arrayContaining([ + link1, + link2, + { ...linkMlLanding, categories: mlNavCategories, links: mlNavLinks }, + ]) ); - - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); - expect(value).toEqual([ - link1, - link2, - { ...linkMlLanding, categories: mlNavCategories, links: mlNavLinks }, - projectLinkDiscover, - projectLinkDevTools, - ]); }); it('should add assets links', async () => { @@ -164,20 +138,10 @@ describe('getProjectNavLinks', () => { }; const testSecurityNavLinks$ = new BehaviorSubject([link1, linkAssets]); - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures + const value = await createTestProjectNavLinks(testSecurityNavLinks$); + expect(value).toEqual( + expect.arrayContaining([link1, { ...linkAssets, links: [...assetsNavLinks, link2] }]) ); - - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); - expect(value).toEqual([ - link1, - { ...linkAssets, links: [...assetsNavLinks, link2] }, - projectLinkDiscover, - projectLinkDevTools, - ]); }); it('should add investigations links', async () => { @@ -189,77 +153,41 @@ describe('getProjectNavLinks', () => { }; const testSecurityNavLinks$ = new BehaviorSubject([link1, linkInvestigations]); - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures + const value = await createTestProjectNavLinks(testSecurityNavLinks$); + expect(value).toEqual( + expect.arrayContaining([ + link1, + { ...linkInvestigations, links: [link2, ...investigationsNavLinks] }, + ]) ); - - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); - expect(value).toEqual([ - link1, - { ...linkInvestigations, links: [link2, ...investigationsNavLinks] }, - projectLinkDiscover, - projectLinkDevTools, - ]); }); it('should add project settings links', async () => { mockChromeNavLinksHas.mockReturnValue(true); // all links exist - const linkProjectSettings: NavigationLink = { - id: SecurityPageName.projectSettings, - title: 'Project settings', - links: [link2], - }; - const testSecurityNavLinks$ = new BehaviorSubject([link1, linkProjectSettings]); - - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures - ); - - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); + const testSecurityNavLinks$ = new BehaviorSubject([link1]); - const expectedProjectSettingsNavLinks = projectSettingsNavLinks.map( - (link) => expect.objectContaining(link) // ignore externalUrl property in cloud links, tested separately + const value = await createTestProjectNavLinks(testSecurityNavLinks$, { + filterCloudLinks: false, + }); + expect(value).toEqual( + expect.arrayContaining([ + link1, + expect.objectContaining({ id: ExternalPageName.management }), + expect.objectContaining({ id: ExternalPageName.integrationsSecurity }), + expect.objectContaining({ id: ExternalPageName.cloudUsersAndRoles }), + expect.objectContaining({ id: ExternalPageName.cloudBilling }), + ]) ); - - expect(value).toEqual([ - link1, - { - ...linkProjectSettings, - categories: projectSettingsNavCategories, - links: [...expectedProjectSettingsNavLinks, link2], - }, - projectLinkDiscover, - projectLinkDevTools, - ]); }); it('should process cloud links', async () => { mockChromeNavLinksHas.mockReturnValue(true); // all links exist - const linkProjectSettings: NavigationLink = { - id: SecurityPageName.projectSettings, - title: 'Project settings', - links: [link2], - }; - const testSecurityNavLinks$ = new BehaviorSubject([link1, linkProjectSettings]); - - const projectNavLinks$ = createProjectNavLinks$( - testSecurityNavLinks$, - testServices, - mockCloudStart, - experimentalFeatures - ); + const testSecurityNavLinks$ = new BehaviorSubject([link1]); - const value = await firstValueFrom(projectNavLinks$.pipe(take(1))); - const cloudLinks = - value - .find((link) => link.id === SecurityPageName.projectSettings) - ?.links?.filter((link) => isCloudLink(link.id)) ?? []; + const value = await createTestProjectNavLinks(testSecurityNavLinks$, { + filterCloudLinks: false, + }); + const cloudLinks = value.filter(({ id }) => isCloudLink(id)); expect(cloudLinks.length > 0).toBe(true); expect(cloudLinks.every((cloudLink) => cloudLink.externalUrl)).toBe(true); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/nav_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/nav_links.ts index 76f4e752af6c5..180d86142abb3 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/nav_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/nav_links.ts @@ -10,26 +10,19 @@ import type { ChromeNavLinks, CoreStart } from '@kbn/core/public'; import { SecurityPageName, type NavigationLink } from '@kbn/security-solution-navigation'; import { isSecurityId } from '@kbn/security-solution-navigation/links'; import type { CloudStart } from '@kbn/cloud-plugin/public'; -import { remove } from 'lodash'; import { assetsNavLinks } from './sections/assets_links'; import { mlNavCategories, mlNavLinks } from './sections/ml_links'; -import { - projectSettingsNavCategories, - projectSettingsNavLinks, -} from './sections/project_settings_links'; +import { projectSettingsNavLinks } from './sections/project_settings_links'; import { devToolsNavLink } from './sections/dev_tools_links'; import { discoverNavLink } from './sections/discover_links'; import type { ProjectNavigationLink } from './types'; import { getCloudLinkKey, getCloudUrl, getNavLinkIdFromProjectPageName, isCloudLink } from './util'; import { investigationsNavLinks } from './sections/investigations_links'; -import { ExternalPageName } from './constants'; -import type { ExperimentalFeatures } from '../../../common/experimental_features'; export const createProjectNavLinks$ = ( securityNavLinks$: Observable>>, core: CoreStart, - cloud: CloudStart, - experimentalFeatures: ExperimentalFeatures + cloud: CloudStart ): Observable => { const { chrome } = core; return combineLatest([securityNavLinks$, chrome.navLinks.getNavLinks$()]).pipe( @@ -38,9 +31,7 @@ export const createProjectNavLinks$ = ( ([securityNavLinks, chromeNavLinks]) => securityNavLinks.length === 0 || chromeNavLinks.length === 0 // skip if not initialized ), - map(([securityNavLinks]) => - processNavLinks(securityNavLinks, chrome.navLinks, cloud, experimentalFeatures) - ) + map(([securityNavLinks]) => processNavLinks(securityNavLinks, chrome.navLinks, cloud)) ); }; @@ -51,8 +42,7 @@ export const createProjectNavLinks$ = ( const processNavLinks = ( securityNavLinks: Array>, chromeNavLinks: ChromeNavLinks, - cloud: CloudStart, - experimentalFeatures: ExperimentalFeatures + cloud: CloudStart ): ProjectNavigationLink[] => { const projectNavLinks: ProjectNavigationLink[] = [...securityNavLinks]; @@ -91,27 +81,9 @@ const processNavLinks = ( }; } - // Project Settings, adding all external sub-links - const projectSettingsLinkIndex = projectNavLinks.findIndex( - ({ id }) => id === SecurityPageName.projectSettings - ); - if (projectSettingsLinkIndex !== -1) { - const projectSettingsNavLink = projectNavLinks[projectSettingsLinkIndex]; - projectNavLinks[projectSettingsLinkIndex] = { - ...projectSettingsNavLink, - categories: projectSettingsNavCategories, - links: [...projectSettingsNavLinks, ...(projectSettingsNavLink.links ?? [])], - }; - } - // Dev Tools. just pushing it projectNavLinks.push(devToolsNavLink); - - if (experimentalFeatures.platformNavEnabled) { - remove(projectNavLinks, { id: SecurityPageName.landing }); - remove(projectNavLinks, { id: ExternalPageName.devTools }); - remove(projectNavLinks, { id: SecurityPageName.projectSettings }); - } + projectNavLinks.push(...projectSettingsNavLinks); return processCloudLinks(filterDisabled(projectNavLinks, chromeNavLinks), cloud); }; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_links.ts index 684304a665fcc..603440016c010 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_links.ts @@ -12,4 +12,5 @@ import { DEV_TOOLS_TITLE } from './dev_tools_translations'; export const devToolsNavLink: ProjectNavigationLink = { id: ExternalPageName.devTools, title: DEV_TOOLS_TITLE, + sideNavIcon: 'editorCodeBlock', }; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_translations.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_translations.ts index 7a4e94a6cd053..898d3efb88829 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_translations.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/dev_tools_translations.ts @@ -10,6 +10,6 @@ import { i18n } from '@kbn/i18n'; export const DEV_TOOLS_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.devTools.title', { - defaultMessage: 'Dev tools', + defaultMessage: 'Developer tools', } ); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts index 69e560a929c09..b5c4d746c2af1 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts @@ -5,194 +5,48 @@ * 2.0. */ -import { LinkCategoryType, SecurityPageName } from '@kbn/security-solution-navigation'; -import { SERVER_APP_ID } from '@kbn/security-solution-plugin/common'; import type { LinkItem } from '@kbn/security-solution-plugin/public'; -import { ExternalPageName, SecurityPagePath } from '../constants'; -import type { ProjectLinkCategory, ProjectNavigationLink } from '../types'; -import { - IconMapServicesLazy, - IconIndexManagementLazy, - IconUsersRolesLazy, - IconReportingLazy, - IconVisualizationLazy, -} from '../../../common/lazy_icons'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { ExternalPageName } from '../constants'; +import type { ProjectNavigationLink } from '../types'; import * as i18n from './project_settings_translations'; -// appLinks configures the Security Solution pages links -const projectSettingsAppLink: LinkItem = { - id: SecurityPageName.projectSettings, - title: i18n.PROJECT_SETTINGS_TITLE, - path: SecurityPagePath[SecurityPageName.projectSettings], - capabilities: [`${SERVER_APP_ID}.show`], - hideTimeline: true, - skipUrlState: true, - links: [], // endpoints and cloudDefend links are added in createAssetsLinkFromManage -}; - -export const createProjectSettingsLinkFromManage = (manageLink: LinkItem): LinkItem => { - const projectSettingsSubLinks = []; - +export const createProjectSettingsLinksFromManage = (manageLink: LinkItem): LinkItem[] => { const entityAnalyticsLink = manageLink.links?.find( ({ id }) => id === SecurityPageName.entityAnalyticsManagement ); - if (entityAnalyticsLink) { - projectSettingsSubLinks.push(entityAnalyticsLink); - } - - return { - ...projectSettingsAppLink, - links: projectSettingsSubLinks, - }; + return entityAnalyticsLink ? [{ ...entityAnalyticsLink, sideNavDisabled: true }] : []; }; -export const projectSettingsNavCategories: ProjectLinkCategory[] = [ - { - type: LinkCategoryType.separator, - linkIds: [ - ExternalPageName.cloudUsersAndRoles, - ExternalPageName.cloudBilling, - SecurityPageName.entityAnalyticsManagement, - ], - }, +export const projectSettingsNavLinks: ProjectNavigationLink[] = [ { - type: LinkCategoryType.separator, - linkIds: [ - ExternalPageName.integrationsSecurity, - ExternalPageName.maps, - ExternalPageName.visualize, - ], + id: ExternalPageName.management, + title: i18n.MANAGEMENT_TITLE, }, { - type: LinkCategoryType.accordion, - label: i18n.MANAGEMENT_CATEGORY_TITLE, - categories: [ - { - label: i18n.DATA_CATEGORY_TITLE, - linkIds: [ - ExternalPageName.managementIndexManagement, - ExternalPageName.managementTransforms, - ExternalPageName.managementIngestPipelines, - ExternalPageName.managementDataViews, - ExternalPageName.managementJobsListLink, - ExternalPageName.managementPipelines, - ], - }, - { - label: i18n.ALERTS_INSIGHTS_CATEGORY_TITLE, - linkIds: [ - ExternalPageName.managementCases, - ExternalPageName.managementTriggersActionsConnectors, - ExternalPageName.managementMaintenanceWindows, - ], - }, - { - label: i18n.CONTENT_CATEGORY_TITLE, - linkIds: [ - ExternalPageName.managementObjects, - ExternalPageName.managementFiles, - ExternalPageName.managementReporting, - ExternalPageName.managementTags, - ], - }, - { - label: i18n.OTHER_CATEGORY_TITLE, - linkIds: [ExternalPageName.managementApiKeys, ExternalPageName.managementSettings], - }, - ], + id: ExternalPageName.integrationsSecurity, + title: i18n.INTEGRATIONS_TITLE, }, -]; - -// navLinks define the navigation links for the Security Solution pages and External pages as well -export const projectSettingsNavLinks: ProjectNavigationLink[] = [ { id: ExternalPageName.cloudUsersAndRoles, title: i18n.CLOUD_USERS_ROLES_TITLE, - description: i18n.CLOUD_USERS_ROLES_DESCRIPTION, - landingIcon: IconUsersRolesLazy, }, { - id: ExternalPageName.cloudBilling, - title: i18n.CLOUD_BILLING_TITLE, - description: i18n.CLOUD_BILLING_DESCRIPTION, - landingIcon: IconReportingLazy, + id: ExternalPageName.cloudPerformance, + title: i18n.CLOUD_PERFORMANCE_TITLE, }, { - id: ExternalPageName.integrationsSecurity, - title: i18n.INTEGRATIONS_TITLE, - description: i18n.INTEGRATIONS_DESCRIPTION, - landingIcon: IconIndexManagementLazy, + id: ExternalPageName.cloudBilling, + title: i18n.CLOUD_BILLING_TITLE, }, { id: ExternalPageName.maps, - title: i18n.MAPS_TITLE, - description: i18n.MAPS_DESCRIPTION, - landingIcon: IconMapServicesLazy, + title: i18n.CLOUD_MAPS_TITLE, + disabled: true, // the link will be available in the navigationTree (breadcrumbs) but not appear in the sideNav }, { id: ExternalPageName.visualize, - title: i18n.VISUALIZE_TITLE, - description: i18n.VISUALIZE_DESCRIPTION, - landingIcon: IconVisualizationLazy, - }, - { - id: ExternalPageName.managementIndexManagement, - title: i18n.MANAGEMENT_INDEX_MANAGEMENT_TITLE, - }, - { - id: ExternalPageName.managementTransforms, - title: i18n.MANAGEMENT_TRANSFORMS_TITLE, - }, - { - id: ExternalPageName.managementMaintenanceWindows, - title: i18n.MANAGEMENT_MAINTENANCE_WINDOWS_TITLE, - }, - { - id: ExternalPageName.managementIngestPipelines, - title: i18n.MANAGEMENT_INGEST_PIPELINES_TITLE, - }, - { - id: ExternalPageName.managementDataViews, - title: i18n.MANAGEMENT_DATA_VIEWS_TITLE, - }, - { - id: ExternalPageName.managementJobsListLink, - title: i18n.MANAGEMENT_ML_TITLE, - }, - { - id: ExternalPageName.managementPipelines, - title: i18n.MANAGEMENT_LOGSTASH_PIPELINES_TITLE, - }, - { - id: ExternalPageName.managementCases, - title: i18n.MANAGEMENT_CASES_TITLE, - }, - { - id: ExternalPageName.managementTriggersActionsConnectors, - title: i18n.MANAGEMENT_CONNECTORS_TITLE, - }, - { - id: ExternalPageName.managementReporting, - title: i18n.MANAGEMENT_REPORTING_TITLE, - }, - { - id: ExternalPageName.managementObjects, - title: i18n.MANAGEMENT_SAVED_OBJECTS_TITLE, - }, - { - id: ExternalPageName.managementApiKeys, - title: i18n.MANAGEMENT_API_KEYS_TITLE, - }, - { - id: ExternalPageName.managementTags, - title: i18n.MANAGEMENT_TAGS_TITLE, - }, - { - id: ExternalPageName.managementFiles, - title: i18n.MANAGEMENT_FILES_TITLE, - }, - { - id: ExternalPageName.managementSettings, - title: i18n.MANAGEMENT_SETTINGS_TITLE, + title: i18n.CLOUD_VISUALIZE_TITLE, + disabled: true, // the link will be available in the navigationTree (breadcrumbs) but not appear in the sideNav }, ]; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts index 56ef414457dfa..50b8086bfb0d9 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts @@ -7,195 +7,46 @@ import { i18n } from '@kbn/i18n'; -export const PROJECT_SETTINGS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.title', +export const MANAGEMENT_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.management.title', { - defaultMessage: 'Project settings', + defaultMessage: 'Management', } ); - export const INTEGRATIONS_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.integrations.title', { defaultMessage: 'Integrations', } ); -export const INTEGRATIONS_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.integrations.description', - { - defaultMessage: 'Security integrations', - } -); - export const CLOUD_USERS_ROLES_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.usersAndRoles.title', { - defaultMessage: 'Users & roles', + defaultMessage: 'Users and roles', } ); -export const CLOUD_USERS_ROLES_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.usersAndRoles.description', +export const CLOUD_PERFORMANCE_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.performance.title', { - defaultMessage: 'Users and roles management', + defaultMessage: 'Performance', } ); - export const CLOUD_BILLING_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.billing.title', { - defaultMessage: 'Billing & consumptions', - } -); -export const CLOUD_BILLING_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.billing.description', - { - defaultMessage: 'Billing & consumption page', + defaultMessage: 'Billing and subscription', } ); -export const MANAGEMENT_INDEX_MANAGEMENT_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.indexManagement.title', - { - defaultMessage: 'Index management', - } -); -export const MANAGEMENT_TRANSFORMS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.transforms.title', - { - defaultMessage: 'Transforms', - } -); -export const MANAGEMENT_INGEST_PIPELINES_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.ingestPipelines.title', - { - defaultMessage: 'Ingest pipelines', - } -); -export const MANAGEMENT_DATA_VIEWS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.dataViews.title', - { - defaultMessage: 'Data views', - } -); -export const MANAGEMENT_ML_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.ml.title', - { - defaultMessage: 'Machine learning', - } -); -export const MANAGEMENT_LOGSTASH_PIPELINES_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.logstashPipelines.title', - { - defaultMessage: 'Logstash pipelines', - } -); -export const MANAGEMENT_CASES_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.cases.title', - { - defaultMessage: 'Cases', - } -); -export const MANAGEMENT_CONNECTORS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.connectors.title', - { - defaultMessage: 'Connectors', - } -); -export const MANAGEMENT_SAVED_OBJECTS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.savedObjects.title', - { - defaultMessage: 'Saved objects', - } -); -export const MANAGEMENT_TAGS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.tags.title', - { - defaultMessage: 'Tags', - } -); -export const MANAGEMENT_SETTINGS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.settings.title', - { - defaultMessage: 'Advanced settings', - } -); -export const MANAGEMENT_MAINTENANCE_WINDOWS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.maintenanceWindows.title', - { - defaultMessage: 'Maintenance windows', - } -); -export const MANAGEMENT_REPORTING_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.reporting.title', - { - defaultMessage: 'Reporting', - } -); -export const MANAGEMENT_API_KEYS_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.apiKeys.title', - { - defaultMessage: 'Api keys', - } -); -export const MANAGEMENT_FILES_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.management.files.title', - { - defaultMessage: 'Files', - } -); - -export const MANAGEMENT_CATEGORY_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.category.management', - { - defaultMessage: 'MANAGEMENT', - } -); -export const DATA_CATEGORY_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.subCategory.data', - { - defaultMessage: 'DATA', - } -); -export const ALERTS_INSIGHTS_CATEGORY_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.subCategory.alertsAndInsights', - { - defaultMessage: 'ALERTS AND INSIGHTS', - } -); -export const CONTENT_CATEGORY_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.subCategory.content', - { - defaultMessage: 'CONTENT', - } -); -export const OTHER_CATEGORY_TITLE = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.subCategory.other', - { - defaultMessage: 'OTHER', - } -); -export const MAPS_TITLE = i18n.translate( +export const CLOUD_MAPS_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.maps.title', { defaultMessage: 'Maps', } ); -export const MAPS_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.maps.description', - { - defaultMessage: 'Plot geographic data', - } -); -export const VISUALIZE_TITLE = i18n.translate( +export const CLOUD_VISUALIZE_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.visualize.title', { defaultMessage: 'Visualize library', } ); - -export const VISUALIZE_DESCRIPTION = i18n.translate( - 'xpack.securitySolutionServerless.navLinks.projectSettings.visualize.description', - { - defaultMessage: 'Visualize library page', - } -); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/utils.ts b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/utils.ts index 3a908b9913060..7e682a40856e8 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/utils.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/utils.ts @@ -6,6 +6,7 @@ */ import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { ExternalPageName } from '../links/constants'; import type { ProjectPageName } from '../links/types'; // We need to hide breadcrumbs for some pages (tabs) because they appear duplicated. @@ -31,4 +32,5 @@ const HIDDEN_BREADCRUMBS = new Set([ export const isBreadcrumbHidden = (id: ProjectPageName): boolean => HIDDEN_BREADCRUMBS.has(id) || - id.startsWith('management:'); /* management sub-pages set their breadcrumbs themselves */ + /* management sub-pages set their breadcrumbs themselves, the main Management breadcrumb is configured with our navigationTree definition */ + (id.startsWith(ExternalPageName.management) && id !== ExternalPageName.management); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.test.tsx b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.test.tsx index 532ca9b985d51..c22fce30419de 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.test.tsx @@ -26,17 +26,27 @@ jest.mock('@kbn/security-solution-side-nav', () => ({ SolutionSideNav: (props: unknown) => mockSolutionSideNav(props), })); +const mockSideNavigationFooter = jest.fn((_props: unknown) => ( +
      +)); +jest.mock('./side_navigation_footer', () => ({ + ...jest.requireActual('./side_navigation_footer'), + SideNavigationFooter: (props: unknown) => mockSideNavigationFooter(props), +})); + const sideNavItems = [ { id: SecurityPageName.dashboards, label: 'Dashboards', href: '/dashboards', + position: 'top', onClick: jest.fn(), }, { id: SecurityPageName.alerts, label: 'Alerts', href: '/alerts', + position: 'top', onClick: jest.fn(), }, { @@ -76,17 +86,37 @@ describe('SecuritySideNavigation', () => { expect(component.queryByTestId('solutionSideNav')).toBeInTheDocument(); }); - it('should pass item props to the SolutionSideNav component', () => { + it('should render the SideNav footer when items received', () => { + const component = render(, { + wrapper: I18nProvider, + }); + expect(component.queryByTestId('solutionSideNavFooter')).toBeInTheDocument(); + }); + + it('should pass only top items to the SolutionSideNav component', () => { render(, { wrapper: I18nProvider }); expect(mockSolutionSideNav).toHaveBeenCalledWith( expect.objectContaining({ - items: sideNavItems, + items: [ + expect.objectContaining({ id: SecurityPageName.dashboards }), + expect.objectContaining({ id: SecurityPageName.alerts }), + ], + }) + ); + }); + + it('should pass only bottom items to the SideNavigationFooter component', () => { + render(, { wrapper: I18nProvider }); + + expect(mockSideNavigationFooter).toHaveBeenCalledWith( + expect.objectContaining({ + items: [expect.objectContaining({ id: SecurityPageName.administration })], }) ); }); - it('should have empty selectedId the SolutionSideNav component', () => { + it('should set empty selectedId', () => { render(, { wrapper: I18nProvider }); expect(mockSolutionSideNav).toHaveBeenCalledWith( @@ -94,9 +124,14 @@ describe('SecuritySideNavigation', () => { selectedId: '', }) ); + expect(mockSideNavigationFooter).toHaveBeenCalledWith( + expect.objectContaining({ + activeNodeId: '', + }) + ); }); - it('should have root external selectedId the SolutionSideNav component', () => { + it('should set root external selectedId', () => { const activeNodes = [[{ id: 'dev_tools' }]] as ChromeProjectNavigationNode[][]; render(, { wrapper: I18nProvider }); @@ -105,9 +140,14 @@ describe('SecuritySideNavigation', () => { selectedId: ExternalPageName.devTools, }) ); + expect(mockSideNavigationFooter).toHaveBeenCalledWith( + expect.objectContaining({ + activeNodeId: 'dev_tools', + }) + ); }); - it('should have external page selectedId the SolutionSideNav component', () => { + it('should set external page selectedId', () => { const activeNodes = [[{ id: `ml:overview` }]] as ChromeProjectNavigationNode[][]; render(, { wrapper: I18nProvider }); @@ -116,9 +156,14 @@ describe('SecuritySideNavigation', () => { selectedId: ExternalPageName.mlOverview, }) ); + expect(mockSideNavigationFooter).toHaveBeenCalledWith( + expect.objectContaining({ + activeNodeId: 'ml:overview', + }) + ); }); - it('should internal selectedId the SolutionSideNav component', () => { + it('should set internal selectedId', () => { const activeNodes = [ [{ id: `${APP_UI_ID}:${SecurityPageName.alerts}` }], ] as ChromeProjectNavigationNode[][]; @@ -129,5 +174,10 @@ describe('SecuritySideNavigation', () => { selectedId: SecurityPageName.alerts, }) ); + expect(mockSideNavigationFooter).toHaveBeenCalledWith( + expect.objectContaining({ + activeNodeId: 'securitySolutionUI:alerts', + }) + ); }); }); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.tsx b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.tsx index 43b02ab94824d..d56e98eef3c2d 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.tsx +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation.tsx @@ -6,25 +6,51 @@ */ import React, { useMemo } from 'react'; -import { EuiLoadingSpinner, useEuiTheme } from '@elastic/eui'; +import type { EuiCollapsibleNavItemProps } from '@elastic/eui'; +import { + EuiCollapsibleNavBeta, + EuiCollapsibleNavItem, + EuiLoadingSpinner, + useEuiTheme, +} from '@elastic/eui'; import type { SideNavComponent } from '@kbn/core-chrome-browser'; -import { SolutionNav } from '@kbn/shared-ux-page-solution-nav'; -import { SolutionSideNav } from '@kbn/security-solution-side-nav'; +import type { SolutionSideNavItem } from '@kbn/security-solution-side-nav'; +import { SolutionSideNav, SolutionSideNavItemPosition } from '@kbn/security-solution-side-nav'; import { useObservable } from 'react-use'; +import { css } from '@emotion/react'; +import { partition } from 'lodash/fp'; import { useSideNavItems } from './use_side_nav_items'; import { CATEGORIES } from './categories'; import { getProjectPageNameFromNavLinkId } from '../links/util'; import { useKibana } from '../../common/services'; +import { SideNavigationFooter } from './side_navigation_footer'; + +const getEuiNavItemFromSideNavItem = (sideNavItem: SolutionSideNavItem, selectedId: string) => ({ + id: sideNavItem.id, + title: sideNavItem.label, + isSelected: sideNavItem.id === selectedId, + href: sideNavItem.href, + onClick: sideNavItem.onClick, +}); export const SecuritySideNavigation: SideNavComponent = React.memo(function SecuritySideNavigation({ activeNodes: [activeChromeNodes], }) { - const { hasHeaderBanner$ } = useKibana().services.chrome; + const { chrome } = useKibana().services; const { euiTheme } = useEuiTheme(); + const hasHeaderBanner = useObservable(chrome.hasHeaderBanner$()); + + /** + * TODO: Uncomment this when we have the getIsSideNavCollapsed API available + * const isCollapsed = useObservable(chrome.getIsSideNavCollapsed$()); + */ + const isCollapsed = false; + const items = useSideNavItems(); - const hasHeaderBanner = useObservable(hasHeaderBanner$()); const isLoading = items.length === 0; + // we only care about the first node to highlight a left nav main item + const activeNodeId = activeChromeNodes?.[0].id ?? ''; const panelTopOffset = useMemo( () => @@ -34,30 +60,56 @@ export const SecuritySideNavigation: SideNavComponent = React.memo(function Secu [hasHeaderBanner, euiTheme] ); - const selectedId = useMemo(() => { - const mainNode = activeChromeNodes?.[0]; // we only care about the first node to highlight a left nav main item - return mainNode ? getProjectPageNameFromNavLinkId(mainNode.id) : ''; - }, [activeChromeNodes]); + const selectedId = useMemo( + () => (activeNodeId ? getProjectPageNameFromNavLinkId(activeNodeId) : ''), + [activeNodeId] + ); + + const bodyStyle = css` + padding-left: calc(${euiTheme.size.xl} + ${euiTheme.size.s}); + padding-right: ${euiTheme.size.s}; + `; + + const collapsedNavItems = useMemo(() => { + return CATEGORIES.reduce((links, category) => { + const categoryLinks = items.filter((item) => category.linkIds.includes(item.id)); + links.push(...categoryLinks.map((link) => getEuiNavItemFromSideNavItem(link, selectedId))); + return links; + }, []); + }, [items, selectedId]); + + const [bodyItems, footerItems] = useMemo( + () => partition((item) => item.position === SolutionSideNavItemPosition.top, items), + [items] + ); return isLoading ? ( ) : ( - - - + <> + + + {!isCollapsed && ( +
      + +
      + )} +
      + + + + ); }); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.test.tsx b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.test.tsx new file mode 100644 index 0000000000000..02e4979c1fba2 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.test.tsx @@ -0,0 +1,125 @@ +/* + * 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 { render } from '@testing-library/react'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { SideNavigationFooter } from './side_navigation_footer'; +import { ExternalPageName } from '../links/constants'; +import { I18nProvider } from '@kbn/i18n-react'; +import type { ProjectSideNavItem } from './types'; + +jest.mock('../../common/services'); + +const items: ProjectSideNavItem[] = [ + { + id: SecurityPageName.landing, + label: 'Get Started', + href: '/landing', + }, + { + id: ExternalPageName.devTools, + label: 'Developer tools', + href: '/dev_tools', + }, + { + id: ExternalPageName.management, + label: 'Management', + href: '/management', + }, + { + id: ExternalPageName.integrationsSecurity, + label: 'Integrations', + href: '/integrations', + }, + { + id: ExternalPageName.cloudUsersAndRoles, + label: 'Users and roles', + href: '/cloud/users_and_roles', + }, + { + id: ExternalPageName.cloudBilling, + label: 'Billing and subscription', + href: '/cloud/billing', + }, +]; + +describe('SideNavigationFooter', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render all the items', () => { + const component = render(, { + wrapper: I18nProvider, + }); + + items.forEach((item) => { + expect(component.queryByTestId(`solutionSideNavItemLink-${item.id}`)).toBeInTheDocument(); + }); + }); + + it('should highlight the active node', () => { + const component = render(, { + wrapper: I18nProvider, + }); + + items.forEach((item) => { + const isSelected = component + .queryByTestId(`solutionSideNavItemLink-${item.id}`) + ?.className.includes('isSelected'); + + if (item.id === ExternalPageName.devTools) { + expect(isSelected).toBe(true); + } else { + expect(isSelected).toBe(false); + } + }); + }); + + it('should highlight the active node inside the collapsible', () => { + const component = render(, { + wrapper: I18nProvider, + }); + + items.forEach((item) => { + const isSelected = component + .queryByTestId(`solutionSideNavItemLink-${item.id}`) + ?.className.includes('isSelected'); + + if (item.id === ExternalPageName.management) { + expect(isSelected).toBe(true); + } else { + expect(isSelected).toBe(false); + } + }); + }); + + it('should render closed collapsible if it has no active node', () => { + const component = render(, { + wrapper: I18nProvider, + }); + + const isOpen = component + .queryByTestId('navFooterCollapsible-project-settings') + ?.className.includes('euiAccordion-isOpen'); + + expect(isOpen).toBe(false); + }); + + it('should open collapsible if it has an active node', () => { + const component = render(, { + wrapper: I18nProvider, + }); + + const isOpen = component + .queryByTestId('navFooterCollapsible-project-settings') + ?.className.includes('euiAccordion-isOpen'); + + expect(isOpen).toBe(true); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.tsx b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.tsx new file mode 100644 index 0000000000000..2c8cf2369c50b --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/side_navigation_footer.tsx @@ -0,0 +1,153 @@ +/* + * 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, { useEffect, useMemo, useState } from 'react'; +import type { EuiCollapsibleNavSubItemProps, IconType } from '@elastic/eui'; +import { EuiCollapsibleNavItem } from '@elastic/eui'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { ExternalPageName } from '../links/constants'; +import { getNavLinkIdFromProjectPageName } from '../links/util'; +import type { ProjectSideNavItem } from './types'; + +interface FooterCategory { + type: 'standalone' | 'collapsible'; + title?: string; + icon?: IconType; + linkIds: string[]; +} + +const categories: FooterCategory[] = [ + { type: 'standalone', linkIds: [SecurityPageName.landing, ExternalPageName.devTools] }, + { + type: 'collapsible', + title: 'Project Settings', + icon: 'gear', + linkIds: [ + ExternalPageName.management, + ExternalPageName.integrationsSecurity, + ExternalPageName.cloudUsersAndRoles, + ExternalPageName.cloudPerformance, + ExternalPageName.cloudBilling, + ], + }, +]; + +export const SideNavigationFooter: React.FC<{ + activeNodeId: string; + items: ProjectSideNavItem[]; +}> = ({ activeNodeId, items }) => { + return ( + <> + {categories.map((category, index) => { + const categoryItems = category.linkIds.reduce((acc, linkId) => { + const item = items.find(({ id }) => id === linkId); + if (item) { + acc.push(item); + } + return acc; + }, []); + + if (category.type === 'standalone') { + return ( + + ); + } + if (category.type === 'collapsible') { + return ( + + ); + } + return null; + })} + + ); +}; + +const SideNavigationFooterStandalone: React.FC<{ + items: ProjectSideNavItem[]; + activeNodeId: string; +}> = ({ items, activeNodeId }) => ( + <> + {items.map((item) => ( + + ))} + +); + +const SideNavigationFooterCollapsible: React.FC<{ + title: string; + items: ProjectSideNavItem[]; + activeNodeId: string; + icon?: IconType; +}> = ({ title, icon, items, activeNodeId }) => { + const hasSelected = useMemo( + () => items.some(({ id }) => getNavLinkIdFromProjectPageName(id) === activeNodeId), + [activeNodeId, items] + ); + const [isOpen, setIsOpen] = useState(hasSelected); + const categoryId = useMemo(() => (title ?? '').toLowerCase().replace(' ', '-'), [title]); + + useEffect(() => { + setIsOpen((open) => (!open ? hasSelected : true)); + }, [hasSelected]); + + return ( + { + setIsOpen(open); + }, + }} + items={items.map((item) => formatCollapsibleItem(item, activeNodeId))} + /> + ); +}; + +const formatCollapsibleItem = ( + sideNavItem: ProjectSideNavItem, + activeNodeId: string +): EuiCollapsibleNavSubItemProps => { + return { + 'data-test-subj': `solutionSideNavItemLink-${sideNavItem.id}`, + id: sideNavItem.id, + title: sideNavItem.label, + isSelected: getNavLinkIdFromProjectPageName(sideNavItem.id) === activeNodeId, + href: sideNavItem.href, + ...(sideNavItem.openInNewTab && { target: '_blank' }), + onClick: sideNavItem.onClick, + icon: sideNavItem.iconType, + iconProps: { size: 's' }, + }; +}; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/types.ts b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/types.ts new file mode 100644 index 0000000000000..8e386090ba8dd --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/types.ts @@ -0,0 +1,11 @@ +/* + * 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 { SolutionSideNavItem } from '@kbn/security-solution-side-nav'; +import type { ProjectPageName } from '../links/types'; + +export type ProjectSideNavItem = SolutionSideNavItem; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.test.tsx b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.test.tsx index b9dfb76923d8a..abb82cc209313 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.test.tsx @@ -104,7 +104,6 @@ describe('useSideNavItems', () => { position: 'bottom', onClick: expect.any(Function), iconType: 'launch', - appendSeparator: true, }, ]); }); @@ -129,7 +128,7 @@ describe('useSideNavItems', () => { label: 'Users & Roles', openInNewTab: true, iconType: 'someicon', - position: 'top', + position: 'bottom', }, ]); }); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.ts b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.ts index 9833e6387b485..9b61439712221 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/side_navigation/use_side_nav_items.ts @@ -8,28 +8,33 @@ import { useCallback, useMemo } from 'react'; import { SecurityPageName, type NavigationLink } from '@kbn/security-solution-navigation'; import { useGetLinkProps } from '@kbn/security-solution-navigation/links'; -import { - SolutionSideNavItemPosition, - type SolutionSideNavItem, -} from '@kbn/security-solution-side-nav'; +import { SolutionSideNavItemPosition } from '@kbn/security-solution-side-nav'; import { useNavLinks } from '../../common/hooks/use_nav_links'; import { ExternalPageName } from '../links/constants'; +import type { ProjectSideNavItem } from './types'; +import type { ProjectPageName } from '../links/types'; type GetLinkProps = (link: NavigationLink) => { - href: string & Partial; + href: string & Partial; }; const isBottomNavItem = (id: string) => id === SecurityPageName.landing || - id === SecurityPageName.projectSettings || - id === ExternalPageName.devTools; -const isGetStartedNavItem = (id: string) => id === SecurityPageName.landing; + id === ExternalPageName.devTools || + id === ExternalPageName.management || + id === ExternalPageName.integrationsSecurity || + id === ExternalPageName.cloudUsersAndRoles || + id === ExternalPageName.cloudPerformance || + id === ExternalPageName.cloudBilling; /** * Formats generic navigation links into the shape expected by the `SolutionSideNav` */ -const formatLink = (navLink: NavigationLink, getLinkProps: GetLinkProps): SolutionSideNavItem => { - const items = navLink.links?.reduce((acc, current) => { +const formatLink = ( + navLink: NavigationLink, + getLinkProps: GetLinkProps +): ProjectSideNavItem => { + const items = navLink.links?.reduce((acc, current) => { if (!current.disabled) { acc.push({ id: current.id, @@ -56,25 +61,10 @@ const formatLink = (navLink: NavigationLink, getLinkProps: GetLinkProps): Soluti }; }; -/** - * Formats the get started navigation links into the shape expected by the `SolutionSideNav` - */ -const formatGetStartedLink = ( - navLink: NavigationLink, - getLinkProps: GetLinkProps -): SolutionSideNavItem => ({ - id: navLink.id, - label: navLink.title, - iconType: navLink.sideNavIcon, - position: SolutionSideNavItemPosition.bottom, - ...getLinkProps(navLink), - appendSeparator: true, -}); - /** * Returns all the formatted SideNavItems, including external links */ -export const useSideNavItems = (): SolutionSideNavItem[] => { +export const useSideNavItems = (): ProjectSideNavItem[] => { const navLinks = useNavLinks(); const getKibanaLinkProps = useGetLinkProps(); @@ -94,13 +84,8 @@ export const useSideNavItems = (): SolutionSideNavItem[] => { return useMemo( () => - navLinks.reduce((items, navLink) => { - if (navLink.disabled) { - return items; - } - if (isGetStartedNavItem(navLink.id)) { - items.push(formatGetStartedLink(navLink, getLinkProps)); - } else { + navLinks.reduce((items, navLink) => { + if (!navLink.disabled) { items.push(formatLink(navLink, getLinkProps)); } return items; diff --git a/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx b/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx deleted file mode 100644 index 71809249b266e..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/pages/project_settings.tsx +++ /dev/null @@ -1,61 +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 { EuiHorizontalRule, EuiPageHeader, EuiSpacer } from '@elastic/eui'; -import { - LandingLinksIconsCategories, - LandingLinksIconsCategoriesGroups, -} from '@kbn/security-solution-navigation/landing_links'; -import type { AccordionLinkCategory, NavigationLink } from '@kbn/security-solution-navigation'; -import { - isAccordionLinkCategory, - isSeparatorLinkCategory, - SecurityPageName, -} from '@kbn/security-solution-navigation'; -import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; -import { useNavLink } from '../common/hooks/use_nav_links'; - -export const ProjectSettingsRoute: React.FC = () => { - const projectSettingsLink = useNavLink(SecurityPageName.projectSettings); - const { links = [], categories = [], title } = projectSettingsLink ?? {}; - - const iconLinks = categories.reduce((acc, category) => { - if (isSeparatorLinkCategory(category)) { - const categoryLinks = links.filter(({ id }) => category.linkIds.includes(id)); - - acc.push(...categoryLinks); - } - return acc; - }, []); - - const accordionCategories = (categories.filter((category) => isAccordionLinkCategory(category)) ?? - []) as AccordionLinkCategory[]; - - const separatorCategories = (categories.filter((category) => isSeparatorLinkCategory(category)) ?? - []) as AccordionLinkCategory[]; - - return ( - - - - - - - - - - - - - - ); -}; - -// eslint-disable-next-line import/no-default-export -export default ProjectSettingsRoute; diff --git a/x-pack/plugins/security_solution_serverless/public/pages/routes.tsx b/x-pack/plugins/security_solution_serverless/public/pages/routes.tsx index 91df4306de69c..31639182db8b1 100644 --- a/x-pack/plugins/security_solution_serverless/public/pages/routes.tsx +++ b/x-pack/plugins/security_solution_serverless/public/pages/routes.tsx @@ -29,9 +29,6 @@ const AssetsPage = withSuspense(AssetsPageLazy); const MachineLearningPageLazy = lazy(() => import('./machine_learning')); const MachineLearningPage = withSuspense(MachineLearningPageLazy); -const ProjectSettingsPageLazy = lazy(() => import('./project_settings')); -const ProjectSettingsPage = withSuspense(ProjectSettingsPageLazy); - // Sets the project specific routes for Serverless as extra routes in the Security Solution plugin export const setRoutes = (services: Services) => { const projectRoutes: RouteProps[] = [ @@ -47,10 +44,6 @@ export const setRoutes = (services: Services) => { path: SecurityPagePath[SecurityPageName.mlLanding], component: withServicesProvider(MachineLearningPage, services), }, - { - path: SecurityPagePath[SecurityPageName.projectSettings], - component: withServicesProvider(ProjectSettingsPage, services), - }, ]; services.securitySolution.setExtraRoutes(projectRoutes); }; diff --git a/x-pack/plugins/security_solution_serverless/public/plugin.ts b/x-pack/plugins/security_solution_serverless/public/plugin.ts index 3f11cf02cd5b3..e7e58c42bcaac 100644 --- a/x-pack/plugins/security_solution_serverless/public/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/public/plugin.ts @@ -53,7 +53,7 @@ export class SecuritySolutionServerlessPlugin securitySolution.experimentalFeatures ).features; - setupNavigation(core, setupDeps, this.experimentalFeatures); + setupNavigation(core, setupDeps); return {}; } @@ -73,7 +73,7 @@ export class SecuritySolutionServerlessPlugin dashboardsLandingCallout: getDashboardsLandingCallout(services), }); - startNavigation(services, this.config); + startNavigation(services); setRoutes(services); return {}; diff --git a/x-pack/plugins/security_solution_serverless/public/types.ts b/x-pack/plugins/security_solution_serverless/public/types.ts index cb34701fe6feb..19fa50d3b630a 100644 --- a/x-pack/plugins/security_solution_serverless/public/types.ts +++ b/x-pack/plugins/security_solution_serverless/public/types.ts @@ -38,5 +38,5 @@ export interface SecuritySolutionServerlessPluginStartDeps { export type ServerlessSecurityPublicConfig = Pick< ServerlessSecurityConfigSchema, - 'productTypes' | 'developer' | 'enableExperimental' + 'productTypes' | 'enableExperimental' >; diff --git a/x-pack/plugins/security_solution_serverless/server/config.ts b/x-pack/plugins/security_solution_serverless/server/config.ts index 551fd5976a761..f10c1d0f2c3c6 100644 --- a/x-pack/plugins/security_solution_serverless/server/config.ts +++ b/x-pack/plugins/security_solution_serverless/server/config.ts @@ -8,13 +8,12 @@ import { schema, type TypeOf } from '@kbn/config-schema'; import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; import type { SecuritySolutionPluginSetup } from '@kbn/security-solution-plugin/server/plugin_contract'; -import { developerConfigSchema, productTypes } from '../common/config'; +import { productTypes } from '../common/config'; import type { ExperimentalFeatures } from '../common/experimental_features'; import { parseExperimentalConfigValue } from '../common/experimental_features'; export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), - developer: developerConfigSchema, productTypes, /** * For internal use. A list of string values (comma delimited) that will enable experimental @@ -38,7 +37,6 @@ export const config: PluginConfigDescriptor = { exposeToBrowser: { enableExperimental: true, productTypes: true, - developer: true, }, schema: configSchema, deprecations: ({ renameFromRoot }) => [ diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index ef6c4009f345e..154284ca9fab8 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -20,7 +20,6 @@ "@kbn/security-solution-ess", "@kbn/security-solution-plugin", "@kbn/serverless", - "@kbn/shared-ux-page-solution-nav", "@kbn/security-solution-side-nav", "@kbn/security-solution-navigation", "@kbn/security-solution-upselling", diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts deleted file mode 100644 index ab9d75f4b08c0..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/management.ts +++ /dev/null @@ -1,34 +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 ({ getPageObject }: FtrProviderContext) { - const PageObject = getPageObject('common'); - const svlCommonPage = getPageObject('svlCommonPage'); - - describe('Management', function () { - before(async () => { - await svlCommonPage.login(); - }); - - after(async () => { - await svlCommonPage.forceLogout(); - }); - - it('redirects from common management url to security specific page', async () => { - const SUB_URL = ''; - await PageObject.navigateToUrl('management', SUB_URL, { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - shouldUseHashForSubUrl: false, - }); - - await PageObject.waitUntilUrlIncludes('/security/project_settings'); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/security/index.ts b/x-pack/test_serverless/functional/test_suites/security/index.ts index 8525b10e9b91a..cadf61fdf5eda 100644 --- a/x-pack/test_serverless/functional/test_suites/security/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/index.ts @@ -11,7 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless security UI', function () { loadTestFile(require.resolve('./ftr/landing_page')); loadTestFile(require.resolve('./ftr/navigation')); - loadTestFile(require.resolve('./ftr/management')); loadTestFile(require.resolve('./ftr/cases/attachment_framework')); loadTestFile(require.resolve('./ftr/cases/view_case')); loadTestFile(require.resolve('./ftr/cases/create_case_form')); From 314eb9265636b3bfeefd3f7a17b525ea764f8a51 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Wed, 18 Oct 2023 12:27:55 +0200 Subject: [PATCH 28/49] [Observability AI Assistant] Assorted bug fixes (#168496) Resolves https://github.com/elastic/obs-ai-assistant-team/issues/68 Resolves https://github.com/elastic/obs-ai-assistant-team/issues/43 Resolves https://github.com/elastic/obs-ai-assistant-team/issues/95 Resolves https://github.com/elastic/obs-ai-assistant-team/issues/96 ## Summary This fixes a number of issues: * https://github.com/elastic/obs-ai-assistant-team/issues/68 * https://github.com/elastic/obs-ai-assistant-team/issues/43 * https://github.com/elastic/obs-ai-assistant-team/issues/95 * https://github.com/elastic/obs-ai-assistant-team/issues/96 It also cleans up some DOM nesting issues (using `

      ` tags inside `` elements so the console shows less noise when in dev mode. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/chat/chat_actions_menu.tsx | 36 ++--- .../public/components/chat/chat_body.tsx | 4 +- .../chat/chat_consolidated_items.tsx | 136 ++++++++++++++++ .../components/chat/chat_item_actions.tsx | 14 +- .../components/chat/chat_prompt_editor.tsx | 2 +- .../public/components/chat/chat_timeline.tsx | 65 ++++---- .../components/chat/chat_welcome_panel.tsx | 20 ++- .../components/chat/conversation_list.tsx | 6 + .../components/chat/function_list_popover.tsx | 14 +- .../chat/incorrect_license_panel.tsx | 8 +- .../components/chat/initial_setup_panel.tsx | 57 +++---- .../public/hooks/use_timeline.test.ts | 153 ++++++++++++++---- .../public/hooks/use_timeline.ts | 46 ++++-- .../get_timeline_items_from_conversation.tsx | 8 +- 14 files changed, 407 insertions(+), 162 deletions(-) create mode 100644 x-pack/plugins/observability_ai_assistant/public/components/chat/chat_consolidated_items.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx index d33d622d4a0f8..457106d474806 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_actions_menu.tsx @@ -170,28 +170,26 @@ export function ChatActionsMenu({ content: ( -

      + {i18n.translate( + 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.description.paragraph', + { + defaultMessage: + 'Using a knowledge base is optional but improves the experience of using the Assistant significantly.', + } + )}{' '} + {i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.description.paragraph', + 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.elser.learnMore', { - defaultMessage: - 'Using a knowledge base is optional but improves the experience of using the Assistant significantly.', + defaultMessage: 'Learn more', } - )}{' '} - - {i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.elser.learnMore', - { - defaultMessage: 'Learn more', - } - )} - -

      + )} + diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx index f49ebe8c62b92..5e4aa5e0659eb 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect, useRef, useState } from 'react'; -import { last } from 'lodash'; +import { flatten, last } from 'lodash'; import { EuiFlexGroup, EuiFlexItem, @@ -94,7 +94,7 @@ export function ChatBody({ let footer: React.ReactNode; const isLoading = Boolean( - connectors.loading || knowledgeBase.status.loading || last(timeline.items)?.loading + connectors.loading || knowledgeBase.status.loading || last(flatten(timeline.items))?.loading ); const containerClassName = css` diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_consolidated_items.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_consolidated_items.tsx new file mode 100644 index 0000000000000..346ccfe501f37 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_consolidated_items.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAvatar, EuiButtonIcon, EuiComment, EuiLink } from '@elastic/eui'; +import { css } from '@emotion/css'; +import { ChatItem } from './chat_item'; +import type { ChatTimelineItem, ChatTimelineProps } from './chat_timeline'; + +const noPanelStyle = css` + .euiCommentEvent { + border: none; + } + + .euiCommentEvent__header { + background: transparent; + border-block-end: none; + } + + .euiCommentEvent__body { + display: none; + } + + .euiLink { + padding: 0 8px; + } + + .euiLink:focus { + text-decoration: none; + } + + .euiLink:hover { + text-decoration: underline; + } +`; + +const avatarStyle = css` + cursor: 'pointer'; +`; + +export function ChatConsolidatedItems({ + consolidatedItem, + onFeedback, + onRegenerate, + onEditSubmit, + onStopGenerating, + onActionClick, +}: { + consolidatedItem: ChatTimelineItem[]; + onFeedback: ChatTimelineProps['onFeedback']; + onRegenerate: ChatTimelineProps['onRegenerate']; + onEditSubmit: ChatTimelineProps['onEdit']; + onStopGenerating: ChatTimelineProps['onStopGenerating']; + onActionClick: ChatTimelineProps['onActionClick']; +}) { + const [expanded, setExpanded] = useState(false); + + const handleToggleExpand = () => { + setExpanded(!expanded); + }; + + return ( + <> + + } + event={ + + + {!expanded + ? i18n.translate('xpack.observabilityAiAssistant.chatCollapsedItems.showEvents', { + defaultMessage: 'Show {count} events', + values: { count: consolidatedItem.length }, + }) + : i18n.translate('xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents', { + defaultMessage: 'Hide {count} events', + values: { count: consolidatedItem.length }, + })} + + + } + username="" + actions={ + + } + /> + + {expanded + ? consolidatedItem.map((item, index) => ( + { + onFeedback(item, feedback); + }} + onRegenerateClick={() => { + onRegenerate(item); + }} + onEditSubmit={(message) => onEditSubmit(item, message)} + onStopGeneratingClick={onStopGenerating} + onActionClick={onActionClick} + /> + )) + : null} + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx index 64585d2f52ecc..4995b0163b7be 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx @@ -101,14 +101,12 @@ export function ChatItemActions({ closePopover={() => setIsPopoverOpen(undefined)} > -

      - {i18n.translate( - 'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful', - { - defaultMessage: 'Copied message', - } - )} -

      + {i18n.translate( + 'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful', + { + defaultMessage: 'Copied message', + } + )}
      ) : null} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx index 124927564eec1..21e9e3871205c 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx @@ -102,7 +102,7 @@ export function ChatPromptEditor({ }, [functionEditorLineCount, model]); const handleSubmit = useCallback(async () => { - if (loading || !prompt?.trim()) { + if (loading || (!prompt?.trim() && !selectedFunctionName)) { return; } const currentPrompt = prompt; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx index a50a9984cf40e..e42924e765609 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx @@ -6,16 +6,16 @@ */ import React, { ReactNode } from 'react'; -import { css } from '@emotion/react'; -import { compact } from 'lodash'; +import { css } from '@emotion/css'; import { EuiCommentList } from '@elastic/eui'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { ChatItem } from './chat_item'; import { ChatWelcomePanel } from './chat_welcome_panel'; +import { ChatConsolidatedItems } from './chat_consolidated_items'; import type { Feedback } from '../feedback_buttons'; -import type { Message } from '../../../common'; -import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; -import { ChatActionClickHandler } from './types'; +import { type Message } from '../../../common'; +import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import type { ChatActionClickHandler } from './types'; export interface ChatTimelineItem extends Pick { @@ -38,7 +38,7 @@ export interface ChatTimelineItem } export interface ChatTimelineProps { - items: ChatTimelineItem[]; + items: Array; knowledgeBase: UseKnowledgeBaseResult; onEdit: (item: ChatTimelineItem, message: Message) => Promise; onFeedback: (item: ChatTimelineItem, feedback: Feedback) => void; @@ -56,35 +56,44 @@ export function ChatTimeline({ onStopGenerating, onActionClick, }: ChatTimelineProps) { - const filteredItems = items.filter((item) => !item.display.hide); - return ( - {compact( - filteredItems.map((item, index) => ( - { - onFeedback(item, feedback); - }} - onRegenerateClick={() => { - onRegenerate(item); - }} - onEditSubmit={(message) => { - return onEdit(item, message); - }} - onStopGeneratingClick={onStopGenerating} - onActionClick={onActionClick} - /> - )) + {items.length <= 1 ? ( + + ) : ( + items.map((item, index) => + Array.isArray(item) ? ( + + ) : ( + { + onFeedback(item, feedback); + }} + onRegenerateClick={() => { + onRegenerate(item); + }} + onEditSubmit={(message) => onEdit(item, message)} + onStopGeneratingClick={onStopGenerating} + onActionClick={onActionClick} + /> + ) + ) )} - {filteredItems.length === 1 ? : null} ); } diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx index 8240c6fef4054..2ad5054a254c8 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_welcome_panel.tsx @@ -36,17 +36,15 @@ export function ChatWelcomePanel({ knowledgeBase }: { knowledgeBase: UseKnowledg

-

- {knowledgeBase.status.value?.ready - ? i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbReady', { - defaultMessage: - 'Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.', - }) - : i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbNotReady', { - defaultMessage: - 'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it. Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.', - })} -

+ {knowledgeBase.status.value?.ready + ? i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbReady', { + defaultMessage: + 'Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.', + }) + : i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbNotReady', { + defaultMessage: + 'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it. Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.', + })}
{!knowledgeBase.status.value?.ready ? ( diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx index 08cb45387a9ef..1551c744d4561 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx @@ -119,6 +119,12 @@ export function ConversationList({ conversation.id ? { iconType: 'trash', + 'aria-label': i18n.translate( + 'xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel', + { + defaultMessage: 'Delete', + } + ), onClick: () => { onClickDeleteConversation(conversation.id); }, diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx index a9cf8146e020d..a82ece36739d9 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/function_list_popover.tsx @@ -88,17 +88,13 @@ export function FunctionListPopover({ return ( <> -

- - {option.label}{' '} - -

+ + {option.label}{' '} +
- -

- {option.searchableLabel || ''} -

+ + {option.searchableLabel || ''} ); diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/incorrect_license_panel.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/incorrect_license_panel.tsx index 4897389da005d..ccb30b1e23383 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/incorrect_license_panel.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/incorrect_license_panel.tsx @@ -44,11 +44,9 @@ export function IncorrectLicensePanel() {

{UPGRADE_LICENSE_TITLE}

-

- {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.body', { - defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.', - })} -

+ {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.body', { + defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.', + })}
diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx index b8c99bbfa7cd4..dc6b23a94c1bc 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/initial_setup_panel.tsx @@ -44,12 +44,9 @@ export function InitialSetupPanel({ -

- {i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.title', { - defaultMessage: - 'Start your Al experience with Elastic by completing the steps below.', - })} -

+ {i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.title', { + defaultMessage: 'Start your Al experience with Elastic by completing the steps below.', + })}
@@ -65,8 +62,8 @@ export function InitialSetupPanel({ } )} description={ - -

+ <> + {i18n.translate( 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.description.paragraph1', { @@ -74,16 +71,16 @@ export function InitialSetupPanel({ 'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it.', } )} -

-

+ + {i18n.translate( 'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.description.paragraph2', { defaultMessage: 'This step is optional, you can always do it later.', } )} -

-
+ + } footer={ knowledgeBase.status.value?.ready ? ( @@ -138,17 +135,17 @@ export function InitialSetupPanel({ )} description={ !connectors.connectors?.length ? ( - -

+ <> + {i18n.translate( 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1', { defaultMessage: 'Set up an OpenAI connector with your AI provider.', } )} -

+
-

+ {i18n.translate( 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2', { @@ -169,18 +166,16 @@ export function InitialSetupPanel({ iconType="iInCircle" size="s" /> -

- + + ) : connectors.connectors.length && !connectors.selectedConnector ? ( -

- {i18n.translate( - 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description', - { - defaultMessage: 'Please select a provider.', - } - )} -

+ {i18n.translate( + 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description', + { + defaultMessage: 'Please select a provider.', + } + )}
) : ( '' @@ -212,12 +207,10 @@ export function InitialSetupPanel({ -

- {i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.disclaimer', { - defaultMessage: - 'The AI provider that is configured may collect telemetry when using the Elastic AI Assistant. Contact your AI provider for information on how data is collected.', - })} -

+ {i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.disclaimer', { + defaultMessage: + 'The AI provider that is configured may collect telemetry when using the Elastic AI Assistant. Contact your AI provider for information on how data is collected.', + })}
diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts index 299164e6f52e6..8d8afe6fb9cca 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.test.ts @@ -14,6 +14,7 @@ import { } from '@testing-library/react-hooks'; import { BehaviorSubject, Subject } from 'rxjs'; import { MessageRole } from '../../common'; +import { ChatTimelineItem } from '../components/chat/chat_timeline'; import type { PendingMessage } from '../types'; import { useTimeline, UseTimelineResult } from './use_timeline'; @@ -83,13 +84,35 @@ describe('useTimeline', () => { { message: { role: MessageRole.User, - content: 'Hello', + content: 'hello', }, }, { message: { role: MessageRole.Assistant, - content: 'Goodbye', + content: '', + function_call: { + name: 'recall', + trigger: MessageRole.User, + }, + }, + }, + { + message: { + name: 'recall', + role: MessageRole.User, + content: '', + }, + }, + { + message: { + content: 'goodbye', + function_call: { + name: '', + arguments: '', + trigger: MessageRole.Assistant, + }, + role: MessageRole.Assistant, }, }, ], @@ -98,48 +121,98 @@ describe('useTimeline', () => { }, chatService: { chat: () => {}, + hasRenderFunction: () => {}, }, } as unknown as HookProps, }); }); it('renders the correct timeline items', () => { - expect(hookResult.result.current.items.length).toEqual(3); + expect(hookResult.result.current.items.length).toEqual(4); expect(hookResult.result.current.items[1]).toEqual({ - actions: { - canCopy: true, - canEdit: true, - canRegenerate: false, - canGiveFeedback: false, - }, - display: { - collapsed: false, - hide: false, - }, - role: MessageRole.User, - content: 'Hello', - loading: false, + actions: { canCopy: true, canEdit: true, canGiveFeedback: false, canRegenerate: false }, + content: 'hello', + currentUser: undefined, + display: { collapsed: false, hide: false }, + element: undefined, + function_call: undefined, id: expect.any(String), + loading: false, + role: MessageRole.User, title: '', }); - expect(hookResult.result.current.items[2]).toEqual({ - display: { - collapsed: false, - hide: false, - }, - actions: { - canCopy: true, - canEdit: false, - canRegenerate: true, - canGiveFeedback: false, + expect(hookResult.result.current.items[3]).toEqual({ + actions: { canCopy: true, canEdit: false, canGiveFeedback: false, canRegenerate: true }, + content: 'goodbye', + currentUser: undefined, + display: { collapsed: false, hide: false }, + element: undefined, + function_call: { + arguments: '', + name: '', + trigger: MessageRole.Assistant, }, - role: MessageRole.Assistant, - content: 'Goodbye', - loading: false, id: expect.any(String), + loading: false, + role: MessageRole.Assistant, title: '', }); + + // Items that are function calls are collapsed into an array. + + // 'title' is a component. This throws Jest for a loop. + const collapsedItemsWithoutTitle = ( + hookResult.result.current.items[2] as ChatTimelineItem[] + ).map(({ title, ...rest }) => rest); + + expect(collapsedItemsWithoutTitle).toEqual([ + { + display: { + collapsed: true, + hide: false, + }, + actions: { + canCopy: true, + canEdit: true, + canRegenerate: false, + canGiveFeedback: false, + }, + currentUser: undefined, + function_call: { + name: 'recall', + trigger: MessageRole.User, + }, + role: MessageRole.User, + content: `\`\`\` +{ + \"name\": \"recall\" +} +\`\`\``, + loading: false, + id: expect.any(String), + }, + { + display: { + collapsed: true, + hide: false, + }, + actions: { + canCopy: true, + canEdit: false, + canRegenerate: false, + canGiveFeedback: false, + }, + currentUser: undefined, + function_call: undefined, + role: MessageRole.User, + content: `\`\`\` +{} +\`\`\``, + loading: false, + id: expect.any(String), + }, + ]); }); }); @@ -197,10 +270,16 @@ describe('useTimeline', () => { }); it('adds two items of which the last one is loading', async () => { - expect(hookResult.result.current.items[0].role).toEqual(MessageRole.User); - expect(hookResult.result.current.items[1].role).toEqual(MessageRole.User); + expect((hookResult.result.current.items[0] as ChatTimelineItem).role).toEqual( + MessageRole.User + ); + expect((hookResult.result.current.items[1] as ChatTimelineItem).role).toEqual( + MessageRole.User + ); - expect(hookResult.result.current.items[2].role).toEqual(MessageRole.Assistant); + expect((hookResult.result.current.items[2] as ChatTimelineItem).role).toEqual( + MessageRole.Assistant + ); expect(hookResult.result.current.items[1]).toMatchObject({ role: MessageRole.User, @@ -303,7 +382,9 @@ describe('useTimeline', () => { describe('and it being regenerated', () => { beforeEach(() => { act(() => { - hookResult.result.current.onRegenerate(hookResult.result.current.items[2]); + hookResult.result.current.onRegenerate( + hookResult.result.current.items[2] as ChatTimelineItem + ); subject.next({ message: { role: MessageRole.Assistant, content: '' } }); }); }); @@ -335,7 +416,9 @@ describe('useTimeline', () => { }); act(() => { - hookResult.result.current.onRegenerate(hookResult.result.current.items[2]); + hookResult.result.current.onRegenerate( + hookResult.result.current.items[2] as ChatTimelineItem + ); }); }); @@ -445,7 +528,7 @@ describe('useTimeline', () => { '@timestamp': expect.any(String), message: { content: 'Hello', - role: 'user', + role: MessageRole.User, }, }, ], diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts index 93f1cec5d6c14..7db568a07a99e 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_timeline.ts @@ -19,7 +19,7 @@ import { type Message, } from '../../common/types'; import type { ChatPromptEditorProps } from '../components/chat/chat_prompt_editor'; -import type { ChatTimelineProps } from '../components/chat/chat_timeline'; +import type { ChatTimelineItem, ChatTimelineProps } from '../components/chat/chat_timeline'; import { ChatActionClickType } from '../components/chat/types'; import { EMPTY_CONVERSATION_TITLE } from '../i18n'; import type { ObservabilityAIAssistantChatService, PendingMessage } from '../types'; @@ -101,6 +101,7 @@ export function useTimeline({ const [isFunctionLoading, setIsFunctionLoading] = useState(false); const prevConversationId = usePrevious(conversationId); + useEffect(() => { if (prevConversationId !== conversationId && pendingMessage?.error) { setPendingMessage(undefined); @@ -257,26 +258,27 @@ export function useTimeline({ }); } - const items = useMemo(() => { - if (pendingMessage) { + const itemsWithAddedLoadingStates = useMemo(() => { + // While we're loading we add an empty loading chat item: + if (pendingMessage || isFunctionLoading) { const nextItems = conversationItems.concat({ id: '', actions: { canCopy: true, canEdit: false, canGiveFeedback: false, - canRegenerate: pendingMessage.aborted || !!pendingMessage.error, + canRegenerate: pendingMessage?.aborted || !!pendingMessage?.error, }, display: { collapsed: false, - hide: pendingMessage.message.role === MessageRole.System, + hide: pendingMessage?.message.role === MessageRole.System, }, - content: pendingMessage.message.content, + content: pendingMessage?.message.content, currentUser, - error: pendingMessage.error, - function_call: pendingMessage.message.function_call, - loading: !pendingMessage.aborted && !pendingMessage.error, - role: pendingMessage.message.role, + error: pendingMessage?.error, + function_call: pendingMessage?.message.function_call, + loading: !pendingMessage?.aborted && !pendingMessage?.error, + role: pendingMessage?.message.role || MessageRole.Assistant, title: '', }); @@ -288,6 +290,7 @@ export function useTimeline({ } return conversationItems.map((item, index) => { + // When we're done loading we remove the placeholder item again if (index < conversationItems.length - 1) { return item; } @@ -298,6 +301,29 @@ export function useTimeline({ }); }, [conversationItems, pendingMessage, currentUser, isFunctionLoading]); + const items = useMemo(() => { + const consolidatedChatItems: Array = []; + let currentGroup: ChatTimelineItem[] | null = null; + + for (const item of itemsWithAddedLoadingStates) { + if (item.display.hide || !item) continue; + + if (item.display.collapsed) { + if (currentGroup) { + currentGroup.push(item); + } else { + currentGroup = [item]; + consolidatedChatItems.push(currentGroup); + } + } else { + consolidatedChatItems.push(item); + currentGroup = null; + } + } + + return consolidatedChatItems; + }, [itemsWithAddedLoadingStates]); + useEffect(() => { return () => { subscription?.unsubscribe(); diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx b/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx index 6d8d8ed8fee13..61f2a4fcbd383 100644 --- a/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx @@ -92,7 +92,7 @@ export function getTimelineItemsfromConversation({ ? messages[index - 1].message.function_call : undefined; - const role = message.message.function_call?.trigger || message.message.role; + let role = message.message.function_call?.trigger || message.message.role; const actions = { canCopy: false, @@ -159,8 +159,12 @@ export function getTimelineItemsfromConversation({ content = !element ? convertMessageToMarkdownCodeBlock(message.message) : undefined; + if (prevFunctionCall?.trigger === MessageRole.Assistant) { + role = MessageRole.Assistant; + } + actions.canEdit = false; - display.collapsed = !isError && !element; + display.collapsed = !element; } else if (message.message.function_call) { // User suggested a function title = ( From 80932b81e24a73f36af81b9de85c8b2bf6d65f9a Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 18 Oct 2023 12:42:11 +0200 Subject: [PATCH 29/49] [FTR] Skip flaky serverless summary actions tests on MKI (#169205) ## Summary This PR skips the flaky Alerting API Summary Actions test suite in MKI runs. --- .../test_suites/common/alerting/summary_actions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts index 7daddff58ce76..72c718f9042b5 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts @@ -40,7 +40,10 @@ export default function ({ getService }: FtrProviderContext) { const esClient = getService('es'); const esDeleteAllIndices = getService('esDeleteAllIndices'); - describe('Summary actions', () => { + describe('Summary actions', function () { + // flaky on MKI, see https://github.com/elastic/kibana/issues/169204 + this.tags(['failsOnMKI']); + const RULE_TYPE_ID = '.es-query'; const ALERT_ACTION_INDEX = 'alert-action-es-query'; const ALERT_INDEX = '.alerts-stack.alerts-default'; From 2015c9f6861253029d4c7ee465b5e241ed983318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Wed, 18 Oct 2023 13:45:45 +0200 Subject: [PATCH 30/49] [Enterprise Search] Add missing native connector configurations (#169128) ## Summary Add missing configurations for some native connectors. Added missing Document Level Security for Network,Jira and Confluence Screenshot 2023-10-17 at 17 35 37 Screenshot 2023-10-17 at 17 36 06 Screenshot 2023-10-17 at 17 36 59 Add enumarate and fetch_subsites for Sharepoint Online Screenshot 2023-10-17 at 17 39 27 ### Checklist Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../kbn-search-connectors/types/connectors.ts | 2 +- .../types/native_connectors.ts | 96 ++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/packages/kbn-search-connectors/types/connectors.ts b/packages/kbn-search-connectors/types/connectors.ts index 8f649c15f348f..4df4a0394546e 100644 --- a/packages/kbn-search-connectors/types/connectors.ts +++ b/packages/kbn-search-connectors/types/connectors.ts @@ -68,7 +68,7 @@ export type ConnectorConfiguration = Record< > & { extract_full_html?: { label: string; value: boolean }; // This only exists for Crawler use_document_level_security?: ConnectorConfigProperties; - use_text_extraction_service?: ConnectorConfigProperties; // This only exists for SharePoint Online + use_text_extraction_service?: ConnectorConfigProperties; }; export interface ConnectorScheduling { diff --git a/packages/kbn-search-connectors/types/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts index 399ce74892de5..c618aa8aa70d4 100644 --- a/packages/kbn-search-connectors/types/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -444,6 +444,21 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record Date: Wed, 18 Oct 2023 13:49:16 +0200 Subject: [PATCH 31/49] [Fleet] Task to publish Agent metrics (#168435) ## Summary Closes https://github.com/elastic/ingest-dev/issues/2396 Added a new kibana task that publishes Agent metrics every minute to data streams installed by fleet_server package. Opened the pr for review, there are a few things to finalize, but the core logic won't change much. To test locally: - Install fleet_server package 1.4.0 from [this](https://github.com/elastic/integrations/pull/8145) pr to get the mappings - Start kibana locally, wait for a few minutes for the metrics task to run (every minute) - Go to discover, `metrics-*` index pattern, filter on `data_stream.dataset: fleet_server.*` - Expect data to be populated in `fleet_server.agent_status` and `fleet_server.agent_versions` datasets. image ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- config/serverless.oblt.yml | 3 + config/serverless.security.yml | 4 + test/functional/config.base.js | 3 + .../server/collectors/agent_collectors.ts | 12 +- x-pack/plugins/fleet/server/mocks/index.ts | 1 + x-pack/plugins/fleet/server/plugin.ts | 12 + .../fleet/server/services/app_context.ts | 6 + .../server/services/epm/filtered_packages.ts | 6 +- .../services/epm/packages/install.test.ts | 5 +- .../metrics/fetch_agent_metrics.test.ts | 92 ++++++++ .../services/metrics/fetch_agent_metrics.ts | 180 +++++++++++++++ .../metrics/fleet_metrics_task.test.ts | 218 ++++++++++++++++++ .../services/metrics/fleet_metrics_task.ts | 204 ++++++++++++++++ x-pack/test/functional/config.base.js | 2 + .../check_registered_task_types.ts | 1 + .../cases/attachment_framework.ts | 3 +- 16 files changed, 737 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.test.ts create mode 100644 x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.ts create mode 100644 x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts create mode 100644 x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index 666f8abbf3f87..a1d0edce6f168 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -50,6 +50,9 @@ xpack.fleet.internal.registry.excludePackages: [ xpack.fleet.packages: - name: apm version: latest +# fleet_server package installed to publish agent metrics + - name: fleet_server + version: latest ## Disable APM UI components and API calls xpack.apm.featureFlags.agentConfigurationAvailable: false xpack.apm.featureFlags.configurableIndicesAvailable: false diff --git a/config/serverless.security.yml b/config/serverless.security.yml index d5edac4507c5d..bf6c914bfaa1a 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -50,6 +50,10 @@ xpack.fleet.internal.registry.excludePackages: [ 'symantec', 'cyberark', ] +# fleet_server package installed to publish agent metrics +xpack.fleet.packages: + - name: fleet_server + version: latest xpack.ml.ad.enabled: true xpack.ml.dfa.enabled: true diff --git a/test/functional/config.base.js b/test/functional/config.base.js index c4f1e3695f474..2f0ec71ffbdaa 100644 --- a/test/functional/config.base.js +++ b/test/functional/config.base.js @@ -34,6 +34,9 @@ export default async function ({ readConfigFile }) { // to be re-enabled once kibana/issues/102552 is completed '--xpack.reporting.enabled=false', + + // disable fleet task that writes to metrics.fleet_server.* data streams, impacting functional tests + `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`, ], }, diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index 109f2d5d36c0b..09a4986e0e6f0 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -58,13 +58,13 @@ export const getAgentUsage = async ( }; }; +export interface AgentPerVersion { + version: string; + count: number; +} + export interface AgentData { - agents_per_version: Array< - { - version: string; - count: number; - } & AgentStatus - >; + agents_per_version: Array; agent_checkin_status: { error: number; degraded: number; diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index adc0ecb1931b4..2716fd82b6811 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -84,6 +84,7 @@ export const createAppContextStartContractMock = ( config$, kibanaVersion: '8.99.0', // Fake version :) kibanaBranch: 'main', + kibanaInstanceId: '1', telemetryEventsSender: createMockTelemetryEventsSender(), bulkActionsResolver: {} as any, messageSigningService: createMessageSigningServiceMock(), diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index e0aa5315d8ff9..e6310a0b0da59 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -130,6 +130,8 @@ import { FleetActionsClient, type FleetActionsClientInterface } from './services import type { FilesClientFactory } from './services/files/types'; import { PolicyWatcher } from './services/agent_policy_watch'; import { getPackageSpecTagId } from './services/epm/kibana/assets/tag_assets'; +import { FleetMetricsTask } from './services/metrics/fleet_metrics_task'; +import { fetchAgentMetrics } from './services/metrics/fetch_agent_metrics'; export interface FleetSetupDeps { security: SecurityPluginSetup; @@ -167,6 +169,7 @@ export interface FleetAppContext { isProductionMode: PluginInitializerContext['env']['mode']['prod']; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; kibanaBranch: PluginInitializerContext['env']['packageInfo']['branch']; + kibanaInstanceId: PluginInitializerContext['env']['instanceUuid']; cloud?: CloudSetup; logger?: Logger; httpSetup?: HttpServiceSetup; @@ -251,6 +254,7 @@ export class FleetPlugin private isProductionMode: FleetAppContext['isProductionMode']; private kibanaVersion: FleetAppContext['kibanaVersion']; private kibanaBranch: FleetAppContext['kibanaBranch']; + private kibanaInstanceId: FleetAppContext['kibanaInstanceId']; private httpSetup?: HttpServiceSetup; private securitySetup!: SecurityPluginSetup; private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; @@ -259,6 +263,7 @@ export class FleetPlugin private bulkActionsResolver?: BulkActionsResolver; private fleetUsageSender?: FleetUsageSender; private checkDeletedFilesTask?: CheckDeletedFilesTask; + private fleetMetricsTask?: FleetMetricsTask; private agentService?: AgentService; private packageService?: PackageService; @@ -270,6 +275,7 @@ export class FleetPlugin this.isProductionMode = this.initializerContext.env.mode.prod; this.kibanaVersion = this.initializerContext.env.packageInfo.version; this.kibanaBranch = this.initializerContext.env.packageInfo.branch; + this.kibanaInstanceId = this.initializerContext.env.instanceUuid; this.logger = this.initializerContext.logger.get(); this.configInitialValue = this.initializerContext.config.get(); this.telemetryEventsSender = new TelemetryEventsSender(this.logger.get('telemetry_events')); @@ -440,6 +446,10 @@ export class FleetPlugin this.fleetUsageSender = new FleetUsageSender(deps.taskManager, core, fetch); registerFleetUsageLogger(deps.taskManager, async () => fetchAgentsUsage(core, config)); + const fetchAgents = async (abortController: AbortController) => + await fetchAgentMetrics(core, abortController); + this.fleetMetricsTask = new FleetMetricsTask(deps.taskManager, fetchAgents); + const router: FleetRouter = core.http.createRouter(); // Allow read-only users access to endpoints necessary for Integrations UI // Only some endpoints require superuser so we pass a raw IRouter here @@ -490,6 +500,7 @@ export class FleetPlugin isProductionMode: this.isProductionMode, kibanaVersion: this.kibanaVersion, kibanaBranch: this.kibanaBranch, + kibanaInstanceId: this.kibanaInstanceId, httpSetup: this.httpSetup, cloud: this.cloud, logger: this.logger, @@ -504,6 +515,7 @@ export class FleetPlugin this.fleetUsageSender?.start(plugins.taskManager); this.checkDeletedFilesTask?.start({ taskManager: plugins.taskManager }); startFleetUsageLogger(plugins.taskManager); + this.fleetMetricsTask?.start(plugins.taskManager, core.elasticsearch.client.asInternalUser); const logger = appContextService.getLogger(); diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index c5c99432d8575..24b35b7b10201 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -62,6 +62,7 @@ class AppContextService { private isProductionMode: FleetAppContext['isProductionMode'] = false; private kibanaVersion: FleetAppContext['kibanaVersion'] = kibanaPackageJson.version; private kibanaBranch: FleetAppContext['kibanaBranch'] = kibanaPackageJson.branch; + private kibanaInstanceId: FleetAppContext['kibanaInstanceId'] = ''; private cloud?: CloudSetup; private logger: Logger | undefined; private httpSetup?: HttpServiceSetup; @@ -86,6 +87,7 @@ class AppContextService { this.logger = appContext.logger; this.kibanaVersion = appContext.kibanaVersion; this.kibanaBranch = appContext.kibanaBranch; + this.kibanaInstanceId = appContext.kibanaInstanceId; this.httpSetup = appContext.httpSetup; this.telemetryEventsSender = appContext.telemetryEventsSender; this.savedObjectsTagging = appContext.savedObjectsTagging; @@ -209,6 +211,10 @@ class AppContextService { return this.kibanaBranch; } + public getKibanaInstanceId() { + return this.kibanaInstanceId; + } + public addExternalCallback(type: ExternalCallback[0], callback: ExternalCallback[1]) { if (!this.externalCallbacks.has(type)) { this.externalCallbacks.set(type, new Set()); diff --git a/x-pack/plugins/fleet/server/services/epm/filtered_packages.ts b/x-pack/plugins/fleet/server/services/epm/filtered_packages.ts index 696554a1451a4..4f18f6fbfddb8 100644 --- a/x-pack/plugins/fleet/server/services/epm/filtered_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/filtered_packages.ts @@ -22,12 +22,8 @@ export function getFilteredSearchPackages() { } export function getFilteredInstallPackages() { - const shouldFilterFleetServer = appContextService.getConfig()?.internal?.fleetServerStandalone; const filtered: string[] = []; - // Do not allow to install Fleet server integration if configured to use standalone fleet server - if (shouldFilterFleetServer) { - filtered.push(FLEET_SERVER_PACKAGE); - } + const excludePackages = appContextService.getConfig()?.internal?.registry?.excludePackages ?? []; return filtered.concat(excludePackages); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts index 9030026da7c49..d8249e0190445 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -332,7 +332,7 @@ describe('install', () => { expect(response.status).toEqual('already_installed'); }); - it('should not allow to install fleet_server if internal.fleetServerStandalone is configured', async () => { + it('should allow to install fleet_server if internal.fleetServerStandalone is configured', async () => { jest.mocked(appContextService.getConfig).mockReturnValueOnce({ internal: { fleetServerStandalone: true, @@ -347,8 +347,7 @@ describe('install', () => { esClient: {} as ElasticsearchClient, }); - expect(response.error).toBeDefined(); - expect(response.error?.message).toMatch(/fleet_server installation is not authorized/); + expect(response.status).toEqual('installed'); }); }); diff --git a/x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.test.ts b/x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.test.ts new file mode 100644 index 0000000000000..fee9e7497e0ef --- /dev/null +++ b/x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.test.ts @@ -0,0 +1,92 @@ +/* + * 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 { ElasticsearchClientMock } from '@kbn/core/server/mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import type { CoreSetup } from '@kbn/core/server'; + +import { fetchAgentMetrics } from './fetch_agent_metrics'; + +jest.mock('../../collectors/agent_collectors', () => { + return { + getAgentUsage: jest.fn().mockResolvedValue({}), + }; +}); + +describe('fetchAgentMetrics', () => { + const { createSetup: coreSetupMock } = coreMock; + const abortController = new AbortController(); + let mockCore: CoreSetup; + let esClient: ElasticsearchClientMock; + + beforeEach(async () => { + mockCore = coreSetupMock(); + const [{ elasticsearch }] = await mockCore.getStartServices(); + esClient = elasticsearch.client.asInternalUser as ElasticsearchClientMock; + }); + + it('should fetch agent metrics', async () => { + esClient.search.mockResolvedValue({ + took: 5, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 0, + relation: 'eq', + }, + hits: [], + }, + aggregations: { + versions: { + buckets: [ + { + key: '8.12.0', + doc_count: 1, + }, + ], + }, + upgrade_details: { + buckets: [ + { + key: 'UPG_REQUESTED', + doc_count: 1, + }, + ], + }, + }, + }); + + const result = await fetchAgentMetrics(mockCore, abortController); + + expect(result).toEqual({ + agents: {}, + agents_per_version: [ + { + version: '8.12.0', + count: 1, + }, + ], + upgrading_step: { + downloading: 0, + extracting: 0, + failed: 0, + replacing: 0, + requested: 1, + restarting: 0, + rollback: 0, + scheduled: 0, + watching: 0, + }, + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.ts b/x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.ts new file mode 100644 index 0000000000000..b768213986f3a --- /dev/null +++ b/x-pack/plugins/fleet/server/services/metrics/fetch_agent_metrics.ts @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { CoreSetup } from '@kbn/core/server'; + +import { AGENTS_INDEX } from '../../../common'; + +import type { AgentPerVersion, AgentUsage } from '../../collectors/agent_collectors'; +import { getAgentUsage } from '../../collectors/agent_collectors'; +import { getInternalClients } from '../../collectors/helpers'; +import { appContextService } from '../app_context'; +import { retryTransientEsErrors } from '../epm/elasticsearch/retry'; + +export interface AgentMetrics { + agents: AgentUsage; + agents_per_version: AgentPerVersion[]; + upgrading_step: UpgradingSteps; +} + +export interface UpgradingSteps { + requested: number; + scheduled: number; + downloading: number; + extracting: number; + replacing: number; + restarting: number; + watching: number; + rollback: number; + failed: number; +} + +export const fetchAgentMetrics = async ( + core: CoreSetup, + abortController: AbortController +): Promise => { + const [soClient, esClient] = await getInternalClients(core); + if (!soClient || !esClient) { + return; + } + const usage = { + agents: await getAgentUsage(soClient, esClient), + agents_per_version: await getAgentsPerVersion(esClient, abortController), + upgrading_step: await getUpgradingSteps(esClient, abortController), + }; + return usage; +}; + +export const getAgentsPerVersion = async ( + esClient: ElasticsearchClient, + abortController: AbortController +): Promise => { + try { + const response = await retryTransientEsErrors(() => + esClient.search( + { + index: AGENTS_INDEX, + query: { + bool: { + filter: [ + { + term: { + active: 'true', + }, + }, + ], + }, + }, + size: 0, + aggs: { + versions: { + terms: { field: 'agent.version' }, + }, + }, + }, + { signal: abortController.signal } + ) + ); + return ((response?.aggregations?.versions as any).buckets ?? []).map((bucket: any) => ({ + version: bucket.key, + count: bucket.doc_count, + })); + } catch (error) { + if (error.statusCode === 404) { + appContextService.getLogger().debug('Index .fleet-agents does not exist yet.'); + } else { + throw error; + } + return []; + } +}; + +export const getUpgradingSteps = async ( + esClient: ElasticsearchClient, + abortController: AbortController +): Promise => { + const upgradingSteps = { + requested: 0, + scheduled: 0, + downloading: 0, + extracting: 0, + replacing: 0, + restarting: 0, + watching: 0, + rollback: 0, + failed: 0, + }; + try { + const response = await retryTransientEsErrors(() => + esClient.search( + { + index: AGENTS_INDEX, + query: { + bool: { + filter: [ + { + term: { + active: 'true', + }, + }, + ], + }, + }, + size: 0, + aggs: { + upgrade_details: { + terms: { field: 'upgrade_details.state' }, + }, + }, + }, + { signal: abortController.signal } + ) + ); + ((response?.aggregations?.upgrade_details as any).buckets ?? []).forEach((bucket: any) => { + switch (bucket.key) { + case 'UPG_REQUESTED': + upgradingSteps.requested = bucket.doc_count; + break; + case 'UPG_SCHEDULED': + upgradingSteps.scheduled = bucket.doc_count; + break; + case 'UPG_DOWNLOADING': + upgradingSteps.downloading = bucket.doc_count; + break; + case 'UPG_EXTRACTING': + upgradingSteps.extracting = bucket.doc_count; + break; + case 'UPG_REPLACING': + upgradingSteps.replacing = bucket.doc_count; + break; + case 'UPG_RESTARTING': + upgradingSteps.restarting = bucket.doc_count; + break; + case 'UPG_WATCHING': + upgradingSteps.watching = bucket.doc_count; + break; + case 'UPG_ROLLBACK': + upgradingSteps.rollback = bucket.doc_count; + break; + case 'UPG_FAILED': + upgradingSteps.failed = bucket.doc_count; + break; + default: + break; + } + }); + return upgradingSteps; + } catch (error) { + if (error.statusCode === 404) { + appContextService.getLogger().debug('Index .fleet-agents does not exist yet.'); + } else { + throw error; + } + return upgradingSteps; + } +}; diff --git a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts new file mode 100644 index 0000000000000..17c6ede4c3f74 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts @@ -0,0 +1,218 @@ +/* + * 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 { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; +import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import type { CoreSetup } from '@kbn/core/server'; +import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; + +import { appContextService } from '../app_context'; +import { createAppContextStartContractMock } from '../../mocks'; + +import { FleetMetricsTask, TYPE, VERSION } from './fleet_metrics_task'; + +const MOCK_TASK_INSTANCE = { + id: `${TYPE}:${VERSION}`, + runAt: new Date(), + attempts: 0, + ownerId: '', + status: TaskStatus.Running, + startedAt: new Date(), + scheduledAt: new Date(), + retryAt: new Date(), + params: {}, + state: {}, + taskType: TYPE, +}; + +describe('fleet metrics task', () => { + const { createSetup: coreSetupMock } = coreMock; + const { createSetup: tmSetupMock, createStart: tmStartMock } = taskManagerMock; + + let mockContract: ReturnType; + let mockTask: FleetMetricsTask; + let mockCore: CoreSetup; + let mockTaskManagerSetup: jest.Mocked; + let mockFetchAgentMetrics: jest.Mock; + + let esClient: ElasticsearchClientMock; + beforeEach(async () => { + mockContract = createAppContextStartContractMock(); + appContextService.start(mockContract); + mockCore = coreSetupMock(); + mockTaskManagerSetup = tmSetupMock(); + const [{ elasticsearch }] = await mockCore.getStartServices(); + esClient = elasticsearch.client.asInternalUser as ElasticsearchClientMock; + mockFetchAgentMetrics = jest.fn(); + mockTask = new FleetMetricsTask(mockTaskManagerSetup, async () => mockFetchAgentMetrics()); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('task lifecycle', () => { + it('should create task', () => { + expect(mockTask).toBeInstanceOf(FleetMetricsTask); + }); + + it('should register task', () => { + expect(mockTaskManagerSetup.registerTaskDefinitions).toHaveBeenCalled(); + }); + + it('should schedule task', async () => { + const mockTaskManagerStart = tmStartMock(); + await mockTask.start(mockTaskManagerStart, esClient); + expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled(); + }); + }); + + describe('task logic', () => { + beforeEach(async () => { + esClient.info.mockResolvedValue({ + cluster_uuid: 'cluster1', + } as any); + + mockFetchAgentMetrics.mockResolvedValue({ + agents: { + total_all_statuses: 10, + total_enrolled: 5, + unenrolled: 5, + healthy: 1, + offline: 1, + updating: 1, + unhealthy: 1, + inactive: 1, + }, + upgrading_step: { + scheduled: 1, + requested: 1, + }, + agents_per_version: [ + { + version: '8.12.0', + count: 3, + }, + { + version: '8.11.0', + count: 2, + }, + ], + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + const runTask = async (taskInstance = MOCK_TASK_INSTANCE) => { + const mockTaskManagerStart = tmStartMock(); + await mockTask.start(mockTaskManagerStart, esClient); + const createTaskRunner = + mockTaskManagerSetup.registerTaskDefinitions.mock.calls[0][0][TYPE].createTaskRunner; + const taskRunner = createTaskRunner({ taskInstance }); + return taskRunner.run(); + }; + + it('should publish agent metrics', async () => { + await runTask(); + + expect(esClient.index).toHaveBeenCalledWith( + expect.objectContaining({ + index: 'metrics-fleet_server.agent_status-default', + body: expect.objectContaining({ + '@timestamp': expect.any(String), + data_stream: { + dataset: 'fleet_server.agent_status', + type: 'metrics', + namespace: 'default', + }, + cluster: { id: 'cluster1' }, + agent: { id: '1', version: '8.99.0', type: 'kibana' }, + fleet: { + agents: { + total: 10, + enrolled: 5, + unenrolled: 5, + healthy: 1, + offline: 1, + updating: 1, + unhealthy: 1, + inactive: 1, + upgrading_step: { + scheduled: 1, + requested: 1, + }, + }, + }, + }), + }) + ); + + expect(esClient.bulk).toHaveBeenCalledWith({ + index: 'metrics-fleet_server.agent_versions-default', + operations: [ + { create: {} }, + { + '@timestamp': expect.any(String), + agent: { id: '1', type: 'kibana', version: '8.99.0' }, + cluster: { id: 'cluster1' }, + data_stream: { + dataset: 'fleet_server.agent_versions', + namespace: 'default', + type: 'metrics', + }, + fleet: { agent: { count: 3, version: '8.12.0' } }, + }, + { create: {} }, + { + '@timestamp': expect.any(String), + agent: { id: '1', type: 'kibana', version: '8.99.0' }, + cluster: { id: 'cluster1' }, + data_stream: { + dataset: 'fleet_server.agent_versions', + namespace: 'default', + type: 'metrics', + }, + fleet: { agent: { count: 2, version: '8.11.0' } }, + }, + ], + refresh: true, + }); + }); + + it('should log errors from bulk create', async () => { + esClient.bulk.mockResolvedValue({ + errors: true, + items: [ + { + create: { + error: { + reason: 'error from es', + }, + }, + }, + { + create: { + error: { + reason: 'error from es', + }, + }, + }, + ], + } as any); + + await runTask(); + + expect(appContextService.getLogger().warn).toHaveBeenCalledWith( + 'Error occurred while publishing Fleet metrics: Error: error from es' + ); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts new file mode 100644 index 0000000000000..03f44bf96ae96 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts @@ -0,0 +1,204 @@ +/* + * 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 { + ConcreteTaskInstance, + TaskManagerStartContract, + TaskManagerSetupContract, +} from '@kbn/task-manager-plugin/server'; +import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import type { ElasticsearchClient } from '@kbn/core/server'; +import { withSpan } from '@kbn/apm-utils'; + +import { uniq } from 'lodash'; + +import { appContextService } from '../app_context'; + +import type { AgentMetrics } from './fetch_agent_metrics'; + +export const TYPE = 'Fleet-Metrics-Task'; +export const VERSION = '1.0.0'; +const TITLE = 'Fleet Metrics Task'; +const TIMEOUT = '1m'; +const SCOPE = ['fleet']; +const INTERVAL = '1m'; + +export class FleetMetricsTask { + private taskManager?: TaskManagerStartContract; + private wasStarted: boolean = false; + private abortController = new AbortController(); + private esClient?: ElasticsearchClient; + + constructor( + taskManager: TaskManagerSetupContract, + fetchAgentMetrics: (abortController: AbortController) => Promise + ) { + taskManager.registerTaskDefinitions({ + [TYPE]: { + title: TITLE, + timeout: TIMEOUT, + maxAttempts: 1, + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { + return { + run: async () => { + return withSpan({ name: TYPE, type: 'metrics' }, () => + this.runTask(taskInstance, () => fetchAgentMetrics(this.abortController)) + ); + }, + + cancel: async () => { + this.abortController.abort('task timed out'); + }, + }; + }, + }, + }); + } + + private runTask = async ( + taskInstance: ConcreteTaskInstance, + fetchAgentMetrics: () => Promise + ) => { + if (!this.wasStarted) { + appContextService.getLogger().debug('[runTask()] Aborted. Task not started yet'); + return; + } + // Check that this task is current + if (taskInstance.id !== this.taskId) { + throwUnrecoverableError(new Error('Outdated task version for task: ' + taskInstance.id)); + return; + } + if (!this.esClient) { + appContextService.getLogger().info('esClient not set, skipping Fleet metrics task'); + return; + } + appContextService.getLogger().info('Running Fleet metrics task'); + + try { + const agentMetrics = await fetchAgentMetrics(); + if (!agentMetrics) { + return; + } + const { agents_per_version: agentsPerVersion, agents } = agentMetrics; + const clusterInfo = await this.esClient.info(); + const getCommonFields = (dataset: string) => { + return { + data_stream: { + dataset, + type: 'metrics', + namespace: 'default', + }, + agent: { + id: appContextService.getKibanaInstanceId(), + version: appContextService.getKibanaVersion(), + type: 'kibana', + }, + cluster: { + id: clusterInfo?.cluster_uuid ?? '', + }, + }; + }; + const agentStatusDoc = { + '@timestamp': new Date().toISOString(), + ...getCommonFields('fleet_server.agent_status'), + fleet: { + agents: { + total: agents.total_all_statuses, + enrolled: agents.total_enrolled, + unenrolled: agents.unenrolled, + healthy: agents.healthy, + offline: agents.offline, + updating: agents.updating, + unhealthy: agents.unhealthy, + inactive: agents.inactive, + upgrading_step: agentMetrics.upgrading_step, + }, + }, + }; + appContextService + .getLogger() + .trace('Agent status metrics: ' + JSON.stringify(agentStatusDoc)); + await this.esClient.index({ + index: 'metrics-fleet_server.agent_status-default', + body: agentStatusDoc, + refresh: true, + }); + + if (agentsPerVersion.length === 0) return; + + const operations = []; + + for (const byVersion of agentsPerVersion) { + const agentVersionsDoc = { + '@timestamp': new Date().toISOString(), + ...getCommonFields('fleet_server.agent_versions'), + fleet: { + agent: { + version: byVersion.version, + count: byVersion.count, + }, + }, + }; + operations.push( + { + create: {}, + }, + agentVersionsDoc + ); + } + + appContextService.getLogger().trace('Agent versions metrics: ' + JSON.stringify(operations)); + const resp = await this.esClient.bulk({ + operations, + refresh: true, + index: 'metrics-fleet_server.agent_versions-default', + }); + if (resp.errors) { + const errors = uniq( + resp.items + .filter((item) => !!item.create?.error) + .map((item) => item.create?.error?.reason ?? '') + ); + throw new Error(errors.join(', ')); + } + } catch (error) { + appContextService.getLogger().warn('Error occurred while publishing Fleet metrics: ' + error); + } + }; + + private get taskId() { + return `${TYPE}:${VERSION}`; + } + + public async start(taskManager: TaskManagerStartContract, esClient: ElasticsearchClient) { + this.taskManager = taskManager; + this.esClient = esClient; + + if (!taskManager) { + appContextService.getLogger().error('missing required service during start'); + return; + } + + this.wasStarted = true; + + try { + appContextService.getLogger().info(`Task ${this.taskId} scheduled with interval 1h`); + + await this.taskManager.ensureScheduled({ + id: this.taskId, + taskType: TYPE, + schedule: { + interval: INTERVAL, + }, + scope: SCOPE, + state: {}, + params: {}, + }); + } catch (e) { + appContextService.getLogger().error(`Error scheduling task, received error: ${e}`); + } + } +} diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 9315b8779fc0c..95849312e17da 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -51,6 +51,8 @@ export default async function ({ readConfigFile }) { '--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true', '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects, '--savedObjects.allowHttpApiAccess=false', // override default to not allow hiddenFromHttpApis saved objects access to the http APIs see https://github.com/elastic/dev/issues/2200 + // disable fleet task that writes to metrics.fleet_server.* data streams, impacting functional tests + `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`, ], }, uiSettings: { diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index a8ab29da16979..7e48895a9060f 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -45,6 +45,7 @@ export default function ({ getService }: FtrProviderContext) { .filter((t: string) => !TEST_TYPES.includes(t)) .sort(); expect(types).to.eql([ + 'Fleet-Metrics-Task', 'Fleet-Usage-Logger', 'Fleet-Usage-Sender', 'ML:saved-objects-sync', diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts index cafe2d867241d..cd0647a1a35d7 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts @@ -20,7 +20,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const cases = getService('cases'); const find = getService('find'); - describe('Cases persistable attachments', function () { + // failing test https://github.com/elastic/kibana/issues/166592 + describe.skip('Cases persistable attachments', function () { // security_exception: action [indices:data/write/delete/byquery] is unauthorized for user [elastic] with effective roles [superuser] on restricted indices [.kibana_alerting_cases], this action is granted by the index privileges [delete,write,all] this.tags(['failsOnMKI']); describe('lens visualization', () => { From 9a8147d6df5be57990fdcc2247c9a203d11a0ca5 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 18 Oct 2023 07:51:33 -0400 Subject: [PATCH 32/49] [Fleet] Support installing bundled package without providing version (#169027) --- .../epm/packages/bundled_package.test.ts | 89 +++++++++++++++++++ .../services/epm/packages/bundled_packages.ts | 17 +++- .../services/epm/packages/install.test.ts | 22 +++-- .../server/services/epm/packages/install.ts | 8 +- 4 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/bundled_package.test.ts diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bundled_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/bundled_package.test.ts new file mode 100644 index 0000000000000..f44a865e8992c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/bundled_package.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import fs from 'fs/promises'; + +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; + +import { appContextService } from '../../app_context'; + +import { getBundledPackageByPkgKey, getBundledPackages } from './bundled_packages'; + +jest.mock('fs/promises'); +jest.mock('../../app_context'); + +describe('bundledPackages', () => { + beforeAll(() => { + jest.mocked(appContextService.getConfig).mockReturnValue({ + developer: { + bundledPackageLocation: '/tmp/test', + }, + } as any); + jest.mocked(appContextService.getLogger).mockReturnValue(loggingSystemMock.createLogger()); + }); + beforeEach(() => { + jest.mocked(fs.stat).mockResolvedValue({} as any); + jest.mocked(fs.readdir).mockResolvedValue(['apm-8.8.0.zip', 'test-1.0.0.zip'] as any); + jest.mocked(fs.readFile).mockResolvedValue(Buffer.from('TEST')); + }); + + afterEach(() => { + jest.mocked(fs.stat).mockReset(); + }); + describe('getBundledPackages', () => { + it('return an empty array if dir do not exists', async () => { + jest.mocked(fs.stat).mockRejectedValue(new Error('NOTEXISTS')); + const packages = await getBundledPackages(); + expect(packages).toEqual([]); + }); + + it('return packages in bundled directory', async () => { + const packages = await getBundledPackages(); + expect(packages).toEqual([ + { + name: 'apm', + version: '8.8.0', + buffer: Buffer.from('TEST'), + }, + { + name: 'test', + version: '1.0.0', + buffer: Buffer.from('TEST'), + }, + ]); + }); + }); + describe('getBundledPackageByPkgKey', () => { + it('should return package by name if no version is provided', async () => { + const pkg = await getBundledPackageByPkgKey('apm'); + + expect(pkg).toBeDefined(); + expect(pkg).toEqual({ + name: 'apm', + version: '8.8.0', + buffer: Buffer.from('TEST'), + }); + }); + + it('should return package by name and version if version is provided', async () => { + const pkg = await getBundledPackageByPkgKey('apm-8.8.0'); + + expect(pkg).toBeDefined(); + expect(pkg).toEqual({ + name: 'apm', + version: '8.8.0', + buffer: Buffer.from('TEST'), + }); + }); + + it('should return package by name and version if version is provided and do not exists', async () => { + const pkg = await getBundledPackageByPkgKey('apm-8.0.0'); + + expect(pkg).not.toBeDefined(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts index ede8a25ca254f..decd5e977506c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts @@ -11,7 +11,7 @@ import path from 'path'; import type { BundledPackage } from '../../../types'; import { FleetError } from '../../../errors'; import { appContextService } from '../../app_context'; -import { splitPkgKey } from '../registry'; +import { splitPkgKey, pkgToPkgKey } from '../registry'; export async function getBundledPackages(): Promise { const config = appContextService.getConfig(); @@ -57,6 +57,21 @@ export async function getBundledPackages(): Promise { } } +export async function getBundledPackageByPkgKey( + pkgKey: string +): Promise { + const bundledPackages = await getBundledPackages(); + const bundledPackage = bundledPackages.find((pkg) => { + if (pkgKey.includes('-')) { + return pkgToPkgKey(pkg) === pkgKey; + } else { + return pkg.name === pkgKey; + } + }); + + return bundledPackage; +} + export async function getBundledPackageByName(name: string): Promise { const bundledPackages = await getBundledPackages(); const bundledPackage = bundledPackages.find((pkg) => pkg.name === name); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts index d8249e0190445..d21e023ce7cdd 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -22,7 +22,7 @@ import * as Registry from '../registry'; import { createInstallation, handleInstallPackageFailure, installPackage } from './install'; import * as install from './_install_package'; -import { getBundledPackages } from './bundled_packages'; +import { getBundledPackageByPkgKey } from './bundled_packages'; import * as obj from '.'; @@ -80,7 +80,7 @@ jest.mock('../archive', () => { }); jest.mock('../../audit_logging'); -const mockGetBundledPackages = jest.mocked(getBundledPackages); +const mockGetBundledPackageByPkgKey = jest.mocked(getBundledPackageByPkgKey); const mockedAuditLoggingService = jest.mocked(auditLoggingService); describe('createInstallation', () => { @@ -143,13 +143,13 @@ describe('install', () => { } as any) ); - mockGetBundledPackages.mockReset(); + mockGetBundledPackageByPkgKey.mockReset(); (install._installPackage as jest.Mock).mockClear(); }); describe('registry', () => { beforeEach(() => { - mockGetBundledPackages.mockResolvedValue([]); + mockGetBundledPackageByPkgKey.mockResolvedValue(undefined); }); it('should send telemetry on install failure, out of date', async () => { @@ -265,13 +265,11 @@ describe('install', () => { it('should install from bundled package if one exists', async () => { (install._installPackage as jest.Mock).mockResolvedValue({}); - mockGetBundledPackages.mockResolvedValue([ - { - name: 'test_package', - version: '1.0.0', - buffer: Buffer.from('test_package'), - }, - ]); + mockGetBundledPackageByPkgKey.mockResolvedValue({ + name: 'test_package', + version: '1.0.0', + buffer: Buffer.from('test_package'), + }); const response = await installPackage({ spaceId: DEFAULT_SPACE_ID, @@ -428,7 +426,7 @@ describe('handleInstallPackageFailure', () => { jest.mocked(install._installPackage).mockClear(); jest.mocked(install._installPackage).mockResolvedValue({} as any); mockedLogger.error.mockClear(); - mockGetBundledPackages.mockResolvedValue([]); + mockGetBundledPackageByPkgKey.mockResolvedValue(undefined); jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); jest.spyOn(Registry, 'splitPkgKey').mockImplementation((pkgKey: string) => { const [pkgName, pkgVersion] = pkgKey.split('-'); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 78708c7716502..ffd1dfe6379c2 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -95,7 +95,7 @@ import { removeInstallation } from './remove'; import { getPackageSavedObjects } from './get'; import { _installPackage } from './_install_package'; import { removeOldAssets } from './cleanup'; -import { getBundledPackages } from './bundled_packages'; +import { getBundledPackageByPkgKey } from './bundled_packages'; import { withPackageSpan } from './utils'; import { convertStringToTitle, generateDescription } from './custom_integrations/utils'; import { INITIAL_VERSION } from './custom_integrations/constants'; @@ -718,8 +718,6 @@ export async function installPackage(args: InstallPackageParams): Promise Registry.pkgToPkgKey(pkg) === pkgkey - ); + const matchingBundledPackage = await getBundledPackageByPkgKey(pkgkey); if (matchingBundledPackage) { logger.debug( From 85d39d0a34d882b49e02777446b553d4d744e0b5 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Wed, 18 Oct 2023 13:52:24 +0200 Subject: [PATCH 33/49] [EDR Workflows] Degrade agent version (#169217) --- x-pack/test/osquery_cypress/artifact_manager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/osquery_cypress/artifact_manager.ts b/x-pack/test/osquery_cypress/artifact_manager.ts index 54b9a70d37aff..0f5ca38d74978 100644 --- a/x-pack/test/osquery_cypress/artifact_manager.ts +++ b/x-pack/test/osquery_cypress/artifact_manager.ts @@ -6,5 +6,6 @@ */ export async function getLatestVersion(): Promise { - return '8.11.0-SNAPSHOT'; + // temporary solution until newer agents work fine with Docker + return '8.10.4'; } From 980e0cc70452897f6271288bdd0ba7b0085731c3 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 18 Oct 2023 14:04:21 +0200 Subject: [PATCH 34/49] [OpenAI Connector] Track token count for streaming responses (#168440) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 1 + packages/kbn-test/jest-preset.js | 2 +- .../jest_integration_node/jest-preset.js | 2 +- .../server/lib/action_executor.test.ts | 105 ++++++++++++- .../actions/server/lib/action_executor.ts | 105 ++++++++----- ...get_token_count_from_openai_stream.test.ts | 138 ++++++++++++++++++ .../lib/get_token_count_from_openai_stream.ts | 119 +++++++++++++++ .../sub_action_connector.ts | 7 +- .../server/routes/chat/route.ts | 4 +- .../server/service/client/index.ts | 12 +- yarn.lock | 12 ++ 11 files changed, 458 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts create mode 100644 x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts diff --git a/package.json b/package.json index 5f17fe974cdb5..8947a3c3680b0 100644 --- a/package.json +++ b/package.json @@ -905,6 +905,7 @@ "getopts": "^2.2.5", "getos": "^3.1.0", "globby": "^11.1.0", + "gpt-tokenizer": "^2.1.2", "handlebars": "4.7.7", "he": "^1.2.0", "history": "^4.9.0", diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index ed5a174e2db80..8d50a88e23073 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -105,7 +105,7 @@ module.exports = { transformIgnorePatterns: [ // ignore all node_modules except monaco-editor and react-monaco-editor which requires babel transforms to handle dynamic import() // since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842) - '[/\\\\]node_modules(?![\\/\\\\](byte-size|monaco-editor|monaco-yaml|vscode-languageserver-types|react-monaco-editor|d3-interpolate|d3-color|langchain|langsmith|@cfworker))[/\\\\].+\\.js$', + '[/\\\\]node_modules(?![\\/\\\\](byte-size|monaco-editor|monaco-yaml|vscode-languageserver-types|react-monaco-editor|d3-interpolate|d3-color|langchain|langsmith|@cfworker|gpt-tokenizer))[/\\\\].+\\.js$', 'packages/kbn-pm/dist/index.js', '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith))/dist/[/\\\\].+\\.js$', '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith))/dist/util/[/\\\\].+\\.js$', diff --git a/packages/kbn-test/jest_integration_node/jest-preset.js b/packages/kbn-test/jest_integration_node/jest-preset.js index 92b8aedb5ee88..631b2c4f9350e 100644 --- a/packages/kbn-test/jest_integration_node/jest-preset.js +++ b/packages/kbn-test/jest_integration_node/jest-preset.js @@ -22,7 +22,7 @@ module.exports = { // An array of regexp pattern strings that are matched against, matched files will skip transformation: transformIgnorePatterns: [ // since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842) - '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith))[/\\\\].+\\.js$', + '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith|gpt-tokenizer))[/\\\\].+\\.js$', '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith))/dist/[/\\\\].+\\.js$', '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith))/dist/util/[/\\\\].+\\.js$', ], diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index a966605bd3a4f..9b2fd5e987f6c 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -20,6 +20,8 @@ import { asSavedObjectExecutionSource, } from './action_execution_source'; import { securityMock } from '@kbn/security-plugin/server/mocks'; +import { finished } from 'stream/promises'; +import { PassThrough } from 'stream'; const actionExecutor = new ActionExecutor({ isESOCanEncrypt: true }); const services = actionsMock.createServices(); @@ -1837,6 +1839,102 @@ test('writes usage data to event log for OpenAI events', async () => { }); }); +test('writes usage data to event log for streaming OpenAI events', async () => { + const executorMock = setupActionExecutorMock('.gen-ai', { + params: { schema: schema.any() }, + config: { schema: schema.any() }, + secrets: { schema: schema.any() }, + }); + + const stream = new PassThrough(); + + executorMock.mockResolvedValue({ + actionId: '1', + status: 'ok', + // @ts-ignore + data: stream, + }); + + await actionExecutor.execute({ + ...executeParams, + params: { + subActionParams: { + body: JSON.stringify({ + messages: [ + { + role: 'system', + content: 'System message', + }, + { + role: 'user', + content: 'User message', + }, + ], + }), + }, + }, + }); + + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + stream.write( + `data: ${JSON.stringify({ + object: 'chat.completion.chunk', + choices: [{ delta: { content: 'Single' } }], + })}\n` + ); + stream.write(`data: [DONE]`); + + stream.end(); + + await finished(stream); + + await new Promise(process.nextTick); + + expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); + expect(eventLogger.logEvent).toHaveBeenNthCalledWith(2, { + event: { + action: 'execute', + kind: 'action', + outcome: 'success', + }, + kibana: { + action: { + execution: { + uuid: '2', + gen_ai: { + usage: { + completion_tokens: 5, + prompt_tokens: 30, + total_tokens: 35, + }, + }, + }, + name: 'action-1', + id: '1', + }, + alert: { + rule: { + execution: { + uuid: '123abc', + }, + }, + }, + saved_objects: [ + { + id: '1', + namespace: 'some-namespace', + rel: 'primary', + type: 'action', + type_id: '.gen-ai', + }, + ], + space_ids: ['some-namespace'], + }, + message: 'action executed: .gen-ai:1: action-1', + user: { name: 'coolguy', id: '123' }, + }); +}); + test('does not fetches actionInfo if passed as param', async () => { const actionType: jest.Mocked = { id: 'test', @@ -1898,13 +1996,16 @@ test('does not fetches actionInfo if passed as param', async () => { ); }); -function setupActionExecutorMock(actionTypeId = 'test') { +function setupActionExecutorMock( + actionTypeId = 'test', + validationOverride?: ActionType['validate'] +) { const actionType: jest.Mocked = { id: 'test', name: 'Test', minimumLicenseRequired: 'basic', supportedFeatureIds: ['alerting'], - validate: { + validate: validationOverride || { config: { schema: schema.object({ bar: schema.boolean() }) }, secrets: { schema: schema.object({ baz: schema.boolean() }) }, params: { schema: schema.object({ foo: schema.boolean() }) }, diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index d3d57de4cda4e..9cd70d4c7bf91 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -13,6 +13,7 @@ import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin import { SpacesServiceStart } from '@kbn/spaces-plugin/server'; import { IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; import { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { PassThrough, Readable } from 'stream'; import { validateParams, validateConfig, @@ -37,6 +38,7 @@ import { RelatedSavedObjects } from './related_saved_objects'; import { createActionEventLogRecordObject } from './create_action_event_log_record_object'; import { ActionExecutionError, ActionExecutionErrorReason } from './errors/action_execution_error'; import type { ActionsAuthorization } from '../authorization/actions_authorization'; +import { getTokenCountFromOpenAIStream } from './get_token_count_from_openai_stream'; // 1,000,000 nanoseconds in 1 millisecond const Millis2Nanos = 1000 * 1000; @@ -276,8 +278,6 @@ export class ActionExecutor { } } - eventLogger.stopTiming(event); - // allow null-ish return to indicate success const result = rawResult || { actionId, @@ -286,6 +286,48 @@ export class ActionExecutor { event.event = event.event || {}; + const { error, ...resultWithoutError } = result; + + function completeEventLogging() { + eventLogger.stopTiming(event); + + const currentUser = security?.authc.getCurrentUser(request); + + event.user = event.user || {}; + event.user.name = currentUser?.username; + event.user.id = currentUser?.profile_uid; + + if (result.status === 'ok') { + span?.setOutcome('success'); + event.event!.outcome = 'success'; + event.message = `action executed: ${actionLabel}`; + } else if (result.status === 'error') { + span?.setOutcome('failure'); + event.event!.outcome = 'failure'; + event.message = `action execution failure: ${actionLabel}`; + event.error = event.error || {}; + event.error.message = actionErrorToMessage(result); + if (result.error) { + logger.error(result.error, { + tags: [actionTypeId, actionId, 'action-run-failed'], + error: { stack_trace: result.error.stack }, + }); + } + logger.warn(`action execution failure: ${actionLabel}: ${event.error.message}`); + } else { + span?.setOutcome('failure'); + event.event!.outcome = 'failure'; + event.message = `action execution returned unexpected result: ${actionLabel}: "${result.status}"`; + event.error = event.error || {}; + event.error.message = 'action execution returned unexpected result'; + logger.warn( + `action execution failure: ${actionLabel}: returned unexpected result "${result.status}"` + ); + } + + eventLogger.logEvent(event); + } + // start openai extension // add event.kibana.action.execution.openai to event log when OpenAI Connector is executed if (result.status === 'ok' && actionTypeId === '.gen-ai') { @@ -310,45 +352,34 @@ export class ActionExecutor { }, }, }; - } - // end openai extension - const currentUser = security?.authc.getCurrentUser(request); - - event.user = event.user || {}; - event.user.name = currentUser?.username; - event.user.id = currentUser?.profile_uid; - - if (result.status === 'ok') { - span?.setOutcome('success'); - event.event.outcome = 'success'; - event.message = `action executed: ${actionLabel}`; - } else if (result.status === 'error') { - span?.setOutcome('failure'); - event.event.outcome = 'failure'; - event.message = `action execution failure: ${actionLabel}`; - event.error = event.error || {}; - event.error.message = actionErrorToMessage(result); - if (result.error) { - logger.error(result.error, { - tags: [actionTypeId, actionId, 'action-run-failed'], - error: { stack_trace: result.error.stack }, - }); + if (result.data instanceof Readable) { + getTokenCountFromOpenAIStream({ + responseStream: result.data.pipe(new PassThrough()), + body: (validatedParams as { subActionParams: { body: string } }).subActionParams.body, + }) + .then(({ total, prompt, completion }) => { + event.kibana!.action!.execution!.gen_ai!.usage = { + total_tokens: total, + prompt_tokens: prompt, + completion_tokens: completion, + }; + }) + .catch((err) => { + logger.error('Failed to calculate tokens from streaming response'); + logger.error(err); + }) + .finally(() => { + completeEventLogging(); + }); + + return resultWithoutError; } - logger.warn(`action execution failure: ${actionLabel}: ${event.error.message}`); - } else { - span?.setOutcome('failure'); - event.event.outcome = 'failure'; - event.message = `action execution returned unexpected result: ${actionLabel}: "${result.status}"`; - event.error = event.error || {}; - event.error.message = 'action execution returned unexpected result'; - logger.warn( - `action execution failure: ${actionLabel}: returned unexpected result "${result.status}"` - ); } + // end openai extension + + completeEventLogging(); - eventLogger.logEvent(event); - const { error, ...resultWithoutError } = result; return resultWithoutError; } ); diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts new file mode 100644 index 0000000000000..080b7cb5f972f --- /dev/null +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts @@ -0,0 +1,138 @@ +/* + * 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 { Transform } from 'stream'; +import { getTokenCountFromOpenAIStream } from './get_token_count_from_openai_stream'; + +interface StreamMock { + write: (data: string) => void; + fail: () => void; + complete: () => void; + transform: Transform; +} + +function createStreamMock(): StreamMock { + const transform: Transform = new Transform({}); + + return { + write: (data: string) => { + transform.push(`${data}\n`); + }, + fail: () => { + transform.emit('error', new Error('Stream failed')); + transform.end(); + }, + transform, + complete: () => { + transform.end(); + }, + }; +} + +describe('getTokenCountFromOpenAIStream', () => { + let tokens: Awaited>; + let stream: StreamMock; + const body = { + messages: [ + { + role: 'system', + content: 'This is a system message', + }, + { + role: 'user', + content: 'This is a user message', + }, + ], + }; + + const chunk = { + object: 'chat.completion.chunk', + choices: [ + { + delta: { + content: 'Single', + }, + }, + ], + }; + + const PROMPT_TOKEN_COUNT = 36; + const COMPLETION_TOKEN_COUNT = 5; + + beforeEach(() => { + stream = createStreamMock(); + stream.write(`data: ${JSON.stringify(chunk)}`); + }); + + describe('when a stream completes', () => { + beforeEach(async () => { + stream.write('data: [DONE]'); + stream.complete(); + }); + + describe('without function tokens', () => { + beforeEach(async () => { + tokens = await getTokenCountFromOpenAIStream({ + responseStream: stream.transform, + body: JSON.stringify(body), + }); + }); + + it('counts the prompt tokens', () => { + expect(tokens.prompt).toBe(PROMPT_TOKEN_COUNT); + expect(tokens.completion).toBe(COMPLETION_TOKEN_COUNT); + expect(tokens.total).toBe(PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT); + }); + }); + + describe('with function tokens', () => { + beforeEach(async () => { + tokens = await getTokenCountFromOpenAIStream({ + responseStream: stream.transform, + body: JSON.stringify({ + ...body, + functions: [ + { + name: 'my_function', + description: 'My function description', + parameters: { + type: 'object', + properties: { + my_property: { + type: 'boolean', + description: 'My function property', + }, + }, + }, + }, + ], + }), + }); + }); + + it('counts the function tokens', () => { + expect(tokens.prompt).toBeGreaterThan(PROMPT_TOKEN_COUNT); + }); + }); + }); + + describe('when a stream fails', () => { + it('resolves the promise with the correct prompt tokens', async () => { + const tokenPromise = getTokenCountFromOpenAIStream({ + responseStream: stream.transform, + body: JSON.stringify(body), + }); + + stream.fail(); + + await expect(tokenPromise).resolves.toEqual({ + prompt: PROMPT_TOKEN_COUNT, + total: PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT, + completion: COMPLETION_TOKEN_COUNT, + }); + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts new file mode 100644 index 0000000000000..74c89f716171e --- /dev/null +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts @@ -0,0 +1,119 @@ +/* + * 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 { encode } from 'gpt-tokenizer'; +import { isEmpty, omitBy } from 'lodash'; +import { Readable } from 'stream'; +import { finished } from 'stream/promises'; +import { CreateChatCompletionRequest } from 'openai'; + +export async function getTokenCountFromOpenAIStream({ + responseStream, + body, +}: { + responseStream: Readable; + body: string; +}): Promise<{ + total: number; + prompt: number; + completion: number; +}> { + const chatCompletionRequest = JSON.parse(body) as CreateChatCompletionRequest; + + // per https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb + const tokensFromMessages = encode( + chatCompletionRequest.messages + .map( + (msg) => + `<|start|>${msg.role}\n${msg.content}\n${ + msg.name + ? msg.name + : msg.function_call + ? msg.function_call.name + '\n' + msg.function_call.arguments + : '' + }<|end|>` + ) + .join('\n') + ).length; + + // this is an approximation. OpenAI cuts off a function schema + // at a certain level of nesting, so their token count might + // be lower than what we are calculating here. + + const tokensFromFunctions = chatCompletionRequest.functions + ? encode( + chatCompletionRequest.functions + ?.map( + (fn) => + `<|start|>${fn.name}\n${fn.description}\n${JSON.stringify(fn.parameters)}<|end|>` + ) + .join('\n') + ).length + : 0; + + const promptTokens = tokensFromMessages + tokensFromFunctions; + + let responseBody: string = ''; + + responseStream.on('data', (chunk: string) => { + responseBody += chunk.toString(); + }); + + try { + await finished(responseStream); + } catch { + // no need to handle this explicitly + } + + const response = responseBody + .split('\n') + .filter((line) => { + return line.startsWith('data: ') && !line.endsWith('[DONE]'); + }) + .map((line) => { + return JSON.parse(line.replace('data: ', '')); + }) + .filter( + ( + line + ): line is { + choices: Array<{ + delta: { content?: string; function_call?: { name?: string; arguments: string } }; + }>; + } => { + return 'object' in line && line.object === 'chat.completion.chunk'; + } + ) + .reduce( + (prev, line) => { + const msg = line.choices[0].delta!; + prev.content += msg.content || ''; + prev.function_call.name += msg.function_call?.name || ''; + prev.function_call.arguments += msg.function_call?.arguments || ''; + return prev; + }, + { content: '', function_call: { name: '', arguments: '' } } + ); + + const completionTokens = encode( + JSON.stringify( + omitBy( + { + content: response.content || undefined, + function_call: response.function_call.name ? response.function_call : undefined, + }, + isEmpty + ) + ) + ).length; + + return { + prompt: promptTokens, + completion: completionTokens, + total: promptTokens + completionTokens, + }; +} diff --git a/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts b/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts index e421083fd6177..7d3c6e51e844e 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts @@ -20,6 +20,7 @@ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { finished } from 'stream/promises'; import { IncomingMessage } from 'http'; +import { PassThrough } from 'stream'; import { assertURL } from './helpers/validators'; import { ActionsConfigurationUtilities } from '../actions_config'; import { SubAction, SubActionRequestParams } from './types'; @@ -158,11 +159,13 @@ export abstract class SubActionConnector { try { const incomingMessage = error.response.data as IncomingMessage; - incomingMessage.on('data', (chunk) => { + const pt = incomingMessage.pipe(new PassThrough()); + + pt.on('data', (chunk) => { responseBody += chunk.toString(); }); - await finished(incomingMessage); + await finished(pt); error.response.data = JSON.parse(responseBody); } catch { diff --git a/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts index 19ebdcbaedc95..90620156acf37 100644 --- a/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts +++ b/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts @@ -5,10 +5,10 @@ * 2.0. */ import { notImplemented } from '@hapi/boom'; -import { IncomingMessage } from 'http'; import * as t from 'io-ts'; import { toBooleanRt } from '@kbn/io-ts-utils'; import type { CreateChatCompletionResponse } from 'openai'; +import { Readable } from 'stream'; import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route'; import { messageRt } from '../runtime_types'; @@ -38,7 +38,7 @@ const chatRoute = createObservabilityAIAssistantServerRoute({ }), t.partial({ query: t.type({ stream: toBooleanRt }) }), ]), - handler: async (resources): Promise => { + handler: async (resources): Promise => { const { request, params, service } = resources; const client = await service.getClient({ request }); diff --git a/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts b/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts index 3a99e293cd5e2..f0df01d87168d 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts @@ -10,7 +10,6 @@ import type { ActionsClient } from '@kbn/actions-plugin/server'; import type { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { IncomingMessage } from 'http'; import { compact, isEmpty, merge, omit } from 'lodash'; import type { ChatCompletionFunctions, @@ -18,10 +17,11 @@ import type { CreateChatCompletionRequest, CreateChatCompletionResponse, } from 'openai'; +import { PassThrough, Readable } from 'stream'; import { v4 } from 'uuid'; import { - type CompatibleJSONSchema, MessageRole, + type CompatibleJSONSchema, type Conversation, type ConversationCreateRequest, type ConversationUpdateRequest, @@ -116,7 +116,7 @@ export class ObservabilityAIAssistantClient { functions?: Array<{ name: string; description: string; parameters: CompatibleJSONSchema }>; functionCall?: string; stream?: TStream; - }): Promise => { + }): Promise => { const messagesForOpenAI: ChatCompletionRequestMessage[] = compact( messages .filter((message) => message.message.content || message.message.function_call?.name) @@ -195,7 +195,11 @@ export class ObservabilityAIAssistantClient { throw internal(`${executeResult?.message} - ${executeResult?.serviceMessage}`); } - return executeResult.data as any; + const response = stream + ? ((executeResult.data as Readable).pipe(new PassThrough()) as Readable) + : (executeResult.data as CreateChatCompletionResponse); + + return response as any; }; find = async (options?: { query?: string }): Promise<{ conversations: Conversation[] }> => { diff --git a/yarn.lock b/yarn.lock index f61f8f0f14da5..c27be1a28c601 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17856,6 +17856,13 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" +gpt-tokenizer@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/gpt-tokenizer/-/gpt-tokenizer-2.1.2.tgz#14f7ce424cf2309fb5be66e112d1836080c2791a" + integrity sha512-HSuI5d6uey+c7x/VzQlPfCoGrfLyAc28vxWofKbjR9PJHm0AjQGSWkKw/OJnb+8S1g7nzgRsf0WH3dK+NNWYbg== + dependencies: + rfc4648 "^1.5.2" + graceful-fs@4.X, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.8, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -26769,6 +26776,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfc4648@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.2.tgz#cf5dac417dd83e7f4debf52e3797a723c1373383" + integrity sha512-tLOizhR6YGovrEBLatX1sdcuhoSCXddw3mqNVAcKxGJ+J0hFeJ+SjeWCv5UPA/WU3YzWPPuCVYgXBKZUPGpKtg== + rfdc@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" From f612a331a8a7b80eb0f02ee6b629f6dd9b233536 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 18 Oct 2023 15:18:12 +0300 Subject: [PATCH 35/49] [ES|QL] Hides the timeRange text from the lens suggestions that are generated with indices without @timestamp (#169125) ## Summary For ES|QL queries based on indices with no @timestamp we don't time filter so the timerange in the bottom is wrong and confusing. Screenshot 2023-10-17 at 5 56 25 PM ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../unified_histogram/public/chart/histogram.tsx | 1 + .../public/chart/hooks/use_time_range.test.tsx | 14 ++++++++++++++ .../public/chart/hooks/use_time_range.tsx | 12 +++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index 1d91b2a505174..956f4ef86f2a5 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -107,6 +107,7 @@ export function Histogram({ timeRange: getTimeRange(), timeInterval, isPlainRecord, + timeField: dataView.timeFieldName, }); const chartRef = useRef(null); const { height: containerHeight, width: containerWidth } = useResizeObserver(chartRef.current); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx index 176d69d984290..1e86bf5d9614e 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx +++ b/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx @@ -245,6 +245,7 @@ describe('useTimeRange', () => { timeRange, timeInterval, isPlainRecord: true, + timeField: '@timestamp', }) ); expect(result.current.timeRangeDisplay).toMatchInlineSnapshot(` @@ -267,4 +268,17 @@ describe('useTimeRange', () => { `); }); + + it('should not render a text for text based languages when not timeField is provided', () => { + const { result } = renderHook(() => + useTimeRange({ + uiSettings, + bucketInterval, + timeRange, + timeInterval, + isPlainRecord: true, + }) + ); + expect(result.current.timeRangeDisplay).toBeNull(); + }); }); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_time_range.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_time_range.tsx index 089df5124b60a..791d332a3a89f 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_time_range.tsx +++ b/src/plugins/unified_histogram/public/chart/hooks/use_time_range.tsx @@ -21,12 +21,14 @@ export const useTimeRange = ({ timeRange: { from, to }, timeInterval, isPlainRecord, + timeField, }: { uiSettings: IUiSettingsClient; bucketInterval?: UnifiedHistogramBucketInterval; timeRange: TimeRange; timeInterval?: string; isPlainRecord?: boolean; + timeField?: string; }) => { const dateFormat = useMemo(() => uiSettings.get('dateFormat'), [uiSettings]); @@ -44,6 +46,10 @@ export const useTimeRange = ({ ); const timeRangeText = useMemo(() => { + if (!timeField && isPlainRecord) { + return ''; + } + const timeRange = { from: dateMath.parse(from), to: dateMath.parse(to, { roundUp: true }), @@ -70,18 +76,18 @@ export const useTimeRange = ({ }); return `${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${intervalText}`; - }, [bucketInterval?.description, from, isPlainRecord, timeInterval, to, toMoment]); + }, [bucketInterval?.description, from, isPlainRecord, timeField, timeInterval, to, toMoment]); const { euiTheme } = useEuiTheme(); const timeRangeCss = css` padding: 0 ${euiTheme.size.s} 0 ${euiTheme.size.s}; `; - let timeRangeDisplay = ( + let timeRangeDisplay = timeRangeText ? ( {timeRangeText} - ); + ) : null; if (bucketInterval?.scaled) { const toolTipTitle = i18n.translate('unifiedHistogram.timeIntervalWithValueWarning', { From e536a0a7959a1f3d235c214328f75d23e70be7e4 Mon Sep 17 00:00:00 2001 From: Wafaa Nasr Date: Wed, 18 Oct 2023 14:20:24 +0200 Subject: [PATCH 36/49] [Security Solution][Exceptions][API testing] Move and restructures exception groups in the new api integration test folder (#168700) ## Summary - Following the initial work in this [PR](https://github.com/elastic/kibana/pull/166755) - Addresses part of https://github.com/elastic/kibana/issues/151902 for exceptions - https://docs.google.com/document/d/1CRFfDWMzw3ob03euWIvT4-IoiLXjoiPWI8mTBqP4Zks/edit first two rows in the new groups - Relocated the tests promptly without focusing on refactoring, as our priority was to obtain coverage as well as grouping them - Transferred the relevant utilities and updated 'signal' to 'alert.' - Temporarily marked security role-related tests as 'brokenInServerless' until resolution. - Moved tests linked to exceptions requiring a `trail` in ESS and `complete` in Serverless under a new folder called `default` license. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .buildkite/ftr_configs.yml | 19 +- .../security_and_spaces/group2/config.ts | 18 - .../security_and_spaces/group2/index.ts | 15 - .../security_and_spaces/group3/index.ts | 15 - .../security_and_spaces/group7/index.ts | 15 - .../security_and_spaces/group8/index.ts | 15 - .../security_and_spaces/group9/config.ts | 18 - .../security_and_spaces/group9/index.ts | 15 - .../config/serverless/config.base.ts | 4 + .../package.json | 24 +- .../date_numeric_types/configs/ess.config.ts | 23 + .../configs/serverless.config.ts | 16 + .../date_numeric_types}/date.ts | 181 ++++---- .../date_numeric_types}/double.ts | 243 ++++++----- .../date_numeric_types}/float.ts | 243 ++++++----- .../date_numeric_types}/index.ts | 4 +- .../date_numeric_types}/integer.ts | 243 ++++++----- .../ips_text_array/configs/ess.config.ts} | 11 +- .../configs/serverless.config.ts | 16 + .../ips_text_array}/index.ts | 3 +- .../ips_text_array}/ip.ts | 229 +++++----- .../ips_text_array}/ip_array.ts | 229 +++++----- .../ips_text_array}/text_array.ts | 197 +++++---- .../keyword_text_long/configs/ess.config.ts} | 11 +- .../configs/serverless.config.ts | 16 + .../keyword_text_long}/index.ts | 3 +- .../keyword_text_long}/keyword.ts | 249 ++++++----- .../keyword_text_long}/keyword_array.ts | 246 +++++------ .../keyword_text_long}/long.ts | 243 ++++++----- .../keyword_text_long}/text.ts | 289 +++++++------ .../workflows/configs/ess.config.ts} | 10 +- .../workflows/configs/serverless.config.ts | 15 + .../workflows}/create_endpoint_exceptions.ts | 224 +++------- .../workflows}/create_rule_exceptions.ts | 7 +- .../find_rule_exception_references.ts | 230 ++++++++++ .../exceptions/workflows/index.ts | 18 + .../workflows/role_based_add_edit_comments.ts | 250 +++++++++++ .../role_based_rule_exceptions_workflows.ts} | 407 ++++-------------- .../rule_exception_synchronizations.ts | 177 ++++++++ .../rule_creation/configs}/ess.config.ts | 6 +- .../configs}/serverless.config.ts | 6 +- .../rule_creation/create_rules.ts | 9 +- .../rule_creation/index.ts | 2 +- .../detections_response/exceptions/index.ts | 13 - .../test_suites/detections_response/index.ts | 15 - .../{action => actions}/get_slack_action.ts | 0 .../get_web_hook_action.ts | 0 .../utils/actions}/index.ts | 11 +- .../remove_uuid_from_actions.ts | 0 .../{alert => alerts}/create_alerts_index.ts | 0 .../{alert => alerts}/delete_all_alerts.ts | 0 .../utils/alerts/get_alerts_by_id.ts | 53 +++ .../{alert => alerts}/get_alerts_by_ids.ts | 0 .../utils/alerts/get_open_alerts.ts | 36 ++ .../{alert => alerts}/get_query_alerts_ids.ts | 0 .../detections_response/utils/alerts/index.ts | 17 + .../wait_for_alert_to_complete.ts | 0 .../wait_for_alerts_to_be_present.ts | 0 .../utils/count_down_es.ts | 46 ++ .../utils/exception_list_and_item/index.ts | 12 + .../item/create_exception_list_item.ts | 42 ++ .../create_container_with_endpoint_entries.ts | 85 ++++ .../list/create_container_with_entries.ts | 76 ++++ .../create_exception_list.ts | 0 .../delete_exception_list.ts | 0 .../detections_response/utils/index.ts | 38 +- .../utils/refresh_index.ts | 19 + .../utils/{rule => rules}/create_rule.ts | 0 .../create_rule_with_exception_entries.ts | 74 ++++ .../utils/{rule => rules}/delete_all_rules.ts | 0 .../utils/{rule => rules}/delete_rule.ts | 0 .../utils/rules/downgrade_immutable_rule.ts | 43 ++ .../utils/rules/find_immutable_rule_by_id.ts | 44 ++ .../rules/get_eql_rule_for_alert_testing.ts | 27 ++ .../utils/{rule => rules}/get_rule.ts | 0 .../utils/{rule => rules}/get_rule_actions.ts | 0 .../get_rule_for_alert_testing.ts | 0 ...r_alert_testing_with_timestamp_override.ts | 0 .../{rule => rules}/get_simple_ml_rule.ts | 0 .../get_simple_ml_rule_output.ts | 0 .../utils/{rule => rules}/get_simple_rule.ts | 0 .../{rule => rules}/get_simple_rule_output.ts | 0 .../get_simple_rule_output_without_rule_id.ts | 0 .../get_simple_rule_without_rule_id.ts | 0 .../get_threshold_rule_for_alert_testing.ts | 0 .../detections_response/utils/rules/index.ts | 30 ++ .../create_prebuilt_rule_saved_objects.ts | 113 +++++ ...get_prebuilt_rules_and_timelines_status.ts | 31 ++ .../utils/rules/prebuilt_rules/index.ts | 10 + .../install_mock_prebuilt_rules.ts | 28 ++ .../install_prebuilt_rules_and_timelines.ts | 56 +++ .../remove_server_generated_properties.ts | 0 ..._generated_properties_including_rule_id.ts | 0 .../{rule => rules}/wait_for_rule_status.ts | 0 .../tsconfig.json | 3 +- 95 files changed, 3119 insertions(+), 2017 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group2/config.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group2/index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group3/index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group7/index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group8/index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group9/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types}/date.ts (73%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types}/double.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types}/float.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types}/index.ts (82%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types}/integer.ts (70%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group3/config.ts => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts} (59%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array}/index.ts (81%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array}/ip.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array}/ip_array.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array}/text_array.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group8/config.ts => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts} (58%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long}/index.ts (82%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long}/keyword.ts (70%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long}/keyword_array.ts (70%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long}/long.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long}/text.ts (71%) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group7/config.ts => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts} (62%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group2 => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows}/create_endpoint_exceptions.ts (75%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{exceptions/rule_exception => default_license/exceptions/workflows}/create_rule_exceptions.ts (98%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/find_rule_exception_references.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_add_edit_comments.ts rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group3/exceptions_workflows.ts => security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts} (72%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/rule_exception_synchronizations.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{ => default_license/rule_creation/configs}/ess.config.ts (73%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{ => default_license/rule_creation/configs}/serverless.config.ts (60%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{ => default_license}/rule_creation/create_rules.ts (99%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{ => default_license}/rule_creation/index.ts (85%) delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/index.ts delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/index.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{action => actions}/get_slack_action.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{action => actions}/get_web_hook_action.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/{ => detections_response/utils/actions}/index.ts (50%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{action => actions}/remove_uuid_from_actions.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{alert => alerts}/create_alerts_index.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{alert => alerts}/delete_all_alerts.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_id.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{alert => alerts}/get_alerts_by_ids.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{alert => alerts}/get_query_alerts_ids.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{alert => alerts}/wait_for_alert_to_complete.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{alert => alerts}/wait_for_alerts_to_be_present.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/item/create_exception_list_item.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/{exception_list => list}/create_exception_list.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/{exception_list => list}/delete_exception_list.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/refresh_index.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/create_rule.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/delete_all_rules.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/delete_rule.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/downgrade_immutable_rule.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/find_immutable_rule_by_id.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_rule.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_rule_actions.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_rule_for_alert_testing.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_rule_for_alert_testing_with_timestamp_override.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_simple_ml_rule.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_simple_ml_rule_output.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_simple_rule.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_simple_rule_output.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_simple_rule_output_without_rule_id.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_simple_rule_without_rule_id.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/get_threshold_rule_for_alert_testing.ts (100%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_mock_prebuilt_rules.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules_and_timelines.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/remove_server_generated_properties.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/remove_server_generated_properties_including_rule_id.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/{rule => rules}/wait_for_rule_status.ts (100%) diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index c74c3d0cfcddf..f026499502e0d 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -223,14 +223,9 @@ enabled: - x-pack/test/cloud_security_posture_api/config.ts - x-pack/test/detection_engine_api_integration/basic/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group2/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group3/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group4/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group5/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group6/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group7/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group8/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts @@ -451,8 +446,18 @@ enabled: - x-pack/performance/journeys/apm_service_inventory.ts - x-pack/test/custom_branding/config.ts - x-pack/test/profiling_api_integration/cloud/config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/serverless.config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/ess.config.ts + + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts + diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/config.ts deleted file mode 100644 index 2430b8f2148d9..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/config.ts +++ /dev/null @@ -1,18 +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 { FtrConfigProviderContext } from '@kbn/test'; - -// eslint-disable-next-line import/no-default-export -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); - - return { - ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], - }; -} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/index.ts deleted file mode 100644 index f477b23e801f3..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/index.ts +++ /dev/null @@ -1,15 +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 '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 2', function () { - loadTestFile(require.resolve('./create_endpoint_exceptions')); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/index.ts deleted file mode 100644 index 73bf2b2669daf..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/index.ts +++ /dev/null @@ -1,15 +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 '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 3', function () { - loadTestFile(require.resolve('./exceptions_workflows')); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/index.ts deleted file mode 100644 index 96f53c47441ed..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/index.ts +++ /dev/null @@ -1,15 +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 '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 7', function () { - loadTestFile(require.resolve('./exception_operators_data_types')); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/index.ts deleted file mode 100644 index 7182e411a1332..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/index.ts +++ /dev/null @@ -1,15 +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 '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 8', function () { - loadTestFile(require.resolve('./exception_operators_data_types')); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts deleted file mode 100644 index 2430b8f2148d9..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts +++ /dev/null @@ -1,18 +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 { FtrConfigProviderContext } from '@kbn/test'; - -// eslint-disable-next-line import/no-default-export -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); - - return { - ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], - }; -} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/index.ts deleted file mode 100644 index ad5e427c69df8..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/index.ts +++ /dev/null @@ -1,15 +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 '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 9', function () { - loadTestFile(require.resolve('./exception_operators_data_types')); - }); -}; diff --git a/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts b/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts index 3dfa29a28b4de..105701ec61e08 100644 --- a/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts @@ -5,6 +5,7 @@ * 2.0. */ import { FtrConfigProviderContext } from '@kbn/test'; +// import { ES_RESOURCES } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/serverless'; export interface CreateTestConfigOptions { testFiles: string[]; @@ -23,6 +24,9 @@ export function createTestConfig(options: CreateTestConfigOptions) { ...svlSharedConfig.get('kbnTestServer'), serverArgs: [...svlSharedConfig.get('kbnTestServer.serverArgs'), '--serverless=security'], }, + // esServerlessOptions: { + // resources: Object.values(ES_RESOURCES), + // }, testFiles: options.testFiles, junit: options.junit, diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 4cd9e44c89eb6..ed8252f86c2da 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -5,9 +5,25 @@ "private": true, "license": "Elastic License 2.0", "scripts": { - "detectionResponse:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/serverless.config.ts", - "detectionResponse:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", - "detectionResponse:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/ess.config.ts", - "detectionResponse:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/ess.config.ts --grep @ess" + "exception_workflows:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts", + "exception_workflows:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_workflows:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts", + "exception_workflows:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts --grep @ess", + "exception_operators_date_numeric_types:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts", + "exception_operators_date_numeric_types:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_operators_date_numeric_types:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts", + "exception_operators_date_numeric_types:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts --grep @ess", + "exception_operators_keyword_text_long:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts", + "exception_operators_keyword_text_long:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_operators_keyword_text_long:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts", + "exception_operators_keyword_text_long:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts --grep @ess", + "exception_operators_ips_text_array:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts", + "exception_operators_ips_text_array:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_operators_ips_text_array:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts", + "exception_operators_ips_text_array:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts --grep @ess", + "rule_creation:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts", + "rule_creation:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "rule_creation:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts", + "rule_creation:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts --grep @ess" } } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts new file mode 100644 index 0000000000000..ec10972a7062e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../../config/ess/config.base.trial') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine ESS/ Exception Operators Data Types API date_numeric_types Integration Tests', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts new file mode 100644 index 0000000000000..3ec867abab432 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createTestConfig } from '../../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine Serverless/ Exception Operators Data Types date_numeric_types API Integration Tests', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/date.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/date.ts similarity index 73% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/date.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/date.ts index 6e926c7c85d92..3ccad9a60e943 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/date.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/date.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type date', () => { + describe('@serverless @ess Rule exception operators for data type date', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/date'); }); @@ -43,7 +42,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -56,12 +55,12 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the dates from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-01T05:08:53.000Z', '2020-10-02T05:08:53.000Z', @@ -71,7 +70,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 1 single date if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -83,9 +82,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-02T05:08:53.000Z', '2020-10-03T05:08:53.000Z', @@ -94,7 +93,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 dates if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -114,14 +113,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); it('should filter 3 dates if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -149,14 +148,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); }); it('should filter 4 dates if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -192,15 +191,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -212,13 +211,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -230,14 +229,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); }); it('will return 0 results if we exclude two dates', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -257,15 +256,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single date if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -277,9 +276,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-02T05:08:53.000Z', '2020-10-03T05:08:53.000Z', @@ -288,7 +287,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 dates if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -300,14 +299,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); it('should filter 3 dates if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -323,14 +322,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); }); it('should filter 4 dates if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -347,15 +346,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -367,13 +366,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -385,16 +384,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against date', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -405,15 +404,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against date', async () => { - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -424,9 +423,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-01T05:08:53.000Z', '2020-10-02T05:08:53.000Z', @@ -439,7 +438,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 3 results if we have a list that includes 1 date', async () => { await importFile(supertest, log, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -454,9 +453,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-02T05:08:53.000Z', '2020-10-03T05:08:53.000Z', @@ -472,7 +471,7 @@ export default ({ getService }: FtrProviderContext) => { ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -487,9 +486,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-02T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); }); @@ -506,7 +505,7 @@ export default ({ getService }: FtrProviderContext) => { ], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -521,8 +520,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([]); }); }); @@ -530,7 +529,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 date', async () => { await importFile(supertest, log, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -545,9 +544,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); }); @@ -559,7 +558,7 @@ export default ({ getService }: FtrProviderContext) => { ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -574,9 +573,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z']); }); @@ -593,7 +592,7 @@ export default ({ getService }: FtrProviderContext) => { ], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['date']); + const rule = getRuleForAlertTesting(['date']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -608,9 +607,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.date).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.date).sort(); expect(hits).to.eql([ '2020-10-01T05:08:53.000Z', '2020-10-02T05:08:53.000Z', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/double.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/double.ts index 39a878a82f896..20efdb98b631e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/double.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/double.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type double', () => { + describe('@serverless @ess Rule exception operators for data type double', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/double'); await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/double_as_string'); @@ -47,7 +46,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -60,17 +59,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the double from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); it('should filter 1 single double if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -82,14 +81,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 double if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -109,14 +108,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 double if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -144,14 +143,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 double if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -187,15 +186,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -207,13 +206,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -225,14 +224,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0']); }); it('will return 0 results if we exclude two double', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -252,15 +251,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single double if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -272,14 +271,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 double if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -291,14 +290,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 double if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -310,14 +309,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 double if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -329,15 +328,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -349,13 +348,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -367,16 +366,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.3']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against double', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -387,15 +386,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against double', async () => { - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -406,9 +405,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); }); @@ -417,7 +416,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against double values in the data set', () => { it('will return 3 results if we have a list that includes 1 double', async () => { await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -432,15 +431,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 double', async () => { await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -455,9 +454,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.3']); }); @@ -469,7 +468,7 @@ export default ({ getService }: FtrProviderContext) => { ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -484,15 +483,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); }); describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 double', async () => { await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -507,15 +506,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 double', async () => { await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -530,9 +529,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.1', '1.3']); }); @@ -544,7 +543,7 @@ export default ({ getService }: FtrProviderContext) => { ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -559,8 +558,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql([]); }); @@ -569,7 +568,7 @@ export default ({ getService }: FtrProviderContext) => { '1.0', '1.2', ]); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -584,9 +583,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.3']); }); }); @@ -596,7 +595,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against double values in the data set', () => { it('will return 1 result if we have a list that excludes 1 double', async () => { await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -611,15 +610,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 double', async () => { await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -634,9 +633,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.2']); }); @@ -648,7 +647,7 @@ export default ({ getService }: FtrProviderContext) => { ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['double']); + const rule = getRuleForAlertTesting(['double']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -663,9 +662,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); }); @@ -673,7 +672,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 double', async () => { await importFile(supertest, log, 'double', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -688,15 +687,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 double', async () => { await importFile(supertest, log, 'double', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -711,9 +710,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.2']); }); @@ -725,7 +724,7 @@ export default ({ getService }: FtrProviderContext) => { ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -740,9 +739,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); @@ -751,7 +750,7 @@ export default ({ getService }: FtrProviderContext) => { '1.0', '1.2', ]); - const rule = getRuleForSignalTesting(['double_as_string']); + const rule = getRuleForAlertTesting(['double_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -766,9 +765,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.double).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.double).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2']); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/float.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/float.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/float.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/float.ts index e67ae81da2398..25d7d2e83c77f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/float.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/float.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type float', () => { + describe('@serverless @ess Rule exception operators for data type float', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/float'); await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/float_as_string'); @@ -45,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -58,17 +57,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the float from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); it('should filter 1 single float if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -80,14 +79,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 float if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -107,14 +106,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 float if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -142,14 +141,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 float if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -185,15 +184,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -205,13 +204,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -223,14 +222,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0']); }); it('will return 0 results if we exclude two float', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -250,15 +249,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single float if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -270,14 +269,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('should filter 2 float if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -289,14 +288,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.2', '1.3']); }); it('should filter 3 float if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -308,14 +307,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.3']); }); it('should filter 4 float if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -327,15 +326,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -347,13 +346,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -365,16 +364,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.3']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against float', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -385,15 +384,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against float', async () => { - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -404,9 +403,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); }); @@ -415,7 +414,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against float values in the data set', () => { it('will return 3 results if we have a list that includes 1 float', async () => { await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -430,15 +429,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -453,15 +452,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.3']); }); it('will return 0 results if we have a list that includes all float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -476,8 +475,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); }); @@ -485,7 +484,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 float', async () => { await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -500,15 +499,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.2', '1.3']); }); it('will return 2 results if we have a list that includes 2 float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -523,15 +522,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.1', '1.3']); }); it('will return 0 results if we have a list that includes all float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -546,8 +545,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql([]); }); @@ -556,7 +555,7 @@ export default ({ getService }: FtrProviderContext) => { '1.0', '1.2', ]); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -571,9 +570,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.3']); }); }); @@ -583,7 +582,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against float values in the data set', () => { it('will return 1 result if we have a list that excludes 1 float', async () => { await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -598,15 +597,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -621,15 +620,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.2']); }); it('will return 4 results if we have a list that excludes all float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float']); + const rule = getRuleForAlertTesting(['float']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -644,9 +643,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); }); @@ -654,7 +653,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 float', async () => { await importFile(supertest, log, 'float', ['1.0'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -669,15 +668,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0']); }); it('will return 2 results if we have a list that excludes 2 float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.2'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -692,15 +691,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.2']); }); it('will return 4 results if we have a list that excludes all float', async () => { await importFile(supertest, log, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -715,9 +714,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); }); @@ -726,7 +725,7 @@ export default ({ getService }: FtrProviderContext) => { '1.0', '1.2', ]); - const rule = getRuleForSignalTesting(['float_as_string']); + const rule = getRuleForAlertTesting(['float_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -741,9 +740,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.float).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.float).sort(); expect(hits).to.eql(['1.0', '1.1', '1.2']); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/index.ts similarity index 82% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/index.ts index 6b2cf915cc51b..2ba798031818e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/index.ts @@ -4,10 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { describe('Detection exceptions data types and operators', function () { loadTestFile(require.resolve('./date')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/integer.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/integer.ts similarity index 70% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/integer.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/integer.ts index 90ce35d24c200..5df6119486113 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/exception_operators_data_types/integer.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/integer.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type integer', () => { + describe('@serverless @ess Rule exception operators for data type integer', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/integer'); await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/integer_as_string'); @@ -47,7 +46,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -60,17 +59,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the integer from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); it('should filter 1 single integer if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -82,14 +81,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 integer if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -109,14 +108,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 integer if all 3 are as exceptions', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -144,14 +143,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 integer if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -187,15 +186,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -207,13 +206,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -225,14 +224,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1']); }); it('will return 0 results if we exclude two integer', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -252,15 +251,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single integer if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -272,14 +271,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 integer if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -291,14 +290,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 integer if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -310,14 +309,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 integer if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -329,15 +328,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -349,13 +348,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -367,16 +366,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '4']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against integer', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -387,15 +386,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against integer', async () => { - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -406,9 +405,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); }); @@ -417,7 +416,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against integer values in the data set', () => { it('will return 3 results if we have a list that includes 1 integer', async () => { await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -432,15 +431,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 integer', async () => { await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -455,15 +454,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all integer', async () => { await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -478,8 +477,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); }); @@ -487,7 +486,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 integer', async () => { await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -502,15 +501,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 integer', async () => { await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -525,15 +524,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all integer', async () => { await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -548,14 +547,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql([]); }); it('will return 1 result if we have a list which contains the integer range of 1-3', async () => { await importFile(supertest, log, 'integer_range', ['1-3'], 'list_items.txt', ['1', '2']); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -570,9 +569,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['4']); }); }); @@ -582,7 +581,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against integer values in the data set', () => { it('will return 1 result if we have a list that excludes 1 integer', async () => { await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -597,15 +596,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 integer', async () => { await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -620,15 +619,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all integer', async () => { await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer']); + const rule = getRuleForAlertTesting(['integer']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -643,9 +642,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); }); @@ -653,7 +652,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 integer', async () => { await importFile(supertest, log, 'integer', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -668,15 +667,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 integer', async () => { await importFile(supertest, log, 'integer', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -691,15 +690,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all integer', async () => { await importFile(supertest, log, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -714,9 +713,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); @@ -726,7 +725,7 @@ export default ({ getService }: FtrProviderContext) => { '2', '3', ]); - const rule = getRuleForSignalTesting(['integer_as_string']); + const rule = getRuleForAlertTesting(['integer_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -741,9 +740,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.integer).sort(); expect(hits).to.eql(['1', '2', '3']); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts similarity index 59% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group3/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts index 2430b8f2148d9..221f6d883bd35 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts @@ -7,12 +7,17 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine ESS/ Exception Operators Data Types API ips_text_array Integration Tests', + }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts new file mode 100644 index 0000000000000..c81563fdad84b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createTestConfig } from '../../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine Serverless/ Exception Operators Data Types ips_text_array API Integration Tests', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/index.ts similarity index 81% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/index.ts index 22864980e2653..63f4639a96412 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/index.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { describe('Detection exceptions data types and operators', function () { loadTestFile(require.resolve('./text_array')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/ip.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/ip.ts index 43a94d1215958..9e73771d11f09 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/ip.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type ip', () => { + describe('@serverless @ess Rule exception operators for data type ip', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/ip'); }); @@ -43,7 +42,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -56,17 +55,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the ips from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('should filter 1 single ip if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -78,14 +77,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('should filter 2 ips if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -105,14 +104,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -140,14 +139,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); it('should filter 4 ips if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -183,13 +182,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('should filter a CIDR range of "127.0.0.1/30"', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -201,16 +200,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -222,13 +221,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -240,14 +239,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1']); }); it('will return 0 results if we exclude two ips', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -267,15 +266,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single ip if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -287,14 +286,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('should filter 2 ips if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -306,14 +305,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -325,14 +324,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); it('should filter 4 ips if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -344,15 +343,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -364,13 +363,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -382,16 +381,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.4']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against ip', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -402,15 +401,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against ip', async () => { - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -421,9 +420,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); }); }); @@ -431,7 +430,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 3 results if we have a list that includes 1 ip', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -446,15 +445,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); }); it('will return 2 results if we have a list that includes 2 ips', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -469,9 +468,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.2', '127.0.0.4']); }); @@ -483,7 +482,7 @@ export default ({ getService }: FtrProviderContext) => { ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -498,8 +497,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); @@ -509,7 +508,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.2', '127.0.0.3', ]); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -524,9 +523,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); @@ -536,7 +535,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.2', '127.0.0.3', ]); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -551,9 +550,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); @@ -566,7 +565,7 @@ export default ({ getService }: FtrProviderContext) => { 'list_items.txt', ['127.0.0.1', '127.0.0.2', '127.0.0.3'] ); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -581,9 +580,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.4']); }); }); @@ -591,7 +590,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 ip', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -606,15 +605,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1']); }); it('will return 2 results if we have a list that excludes 2 ips', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -629,9 +628,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.3']); }); @@ -643,7 +642,7 @@ export default ({ getService }: FtrProviderContext) => { ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -658,9 +657,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); }); @@ -670,7 +669,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.2', '127.0.0.3', ]); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -685,9 +684,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3']); }); @@ -697,7 +696,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.2', '127.0.0.3', ]); - const rule = getRuleForSignalTesting(['ip']); + const rule = getRuleForAlertTesting(['ip']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -712,9 +711,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3']); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/ip_array.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/ip_array.ts index 0fcdf1a03b14b..12c4eb6d55368 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/ip_array.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type ip', () => { + describe('@serverless @ess Rule exception operators for data type ip', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/ip_as_array'); }); @@ -43,7 +42,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -56,12 +55,12 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the ips from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], @@ -71,7 +70,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 1 single ip if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -83,9 +82,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -94,7 +93,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 ips if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -114,14 +113,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -149,14 +148,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(10)).to.eql([]); }); it('should filter a CIDR range of "127.0.0.1/30"', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -168,9 +167,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -179,7 +178,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter a CIDR range of "127.0.0.4/31"', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -191,16 +190,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -212,13 +211,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -230,14 +229,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']]); }); it('will return just 1 result we excluded 2 from the same array elements', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -255,14 +254,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']]); }); it('will return 0 results if we exclude two ips', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -282,15 +281,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single ip if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -302,9 +301,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -313,7 +312,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 ips if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -325,14 +324,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); it('should filter 3 ips if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -344,16 +343,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(10)).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -365,13 +364,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -383,9 +382,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -395,7 +394,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 1 empty result if matching against ip', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -406,16 +405,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(10)).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 3 results if matching against ip', async () => { - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -426,9 +425,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -440,7 +439,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is in list" operator', () => { it('will return 3 results if we have a list that includes 1 ip', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -455,9 +454,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ [], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -467,7 +466,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 2 results if we have a list that includes 2 ips', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.5'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -482,9 +481,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); @@ -496,7 +495,7 @@ export default ({ getService }: FtrProviderContext) => { ['127.0.0.1', '127.0.0.5', '127.0.0.8'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -511,9 +510,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips.flat(10)).to.eql([]); }); @@ -534,7 +533,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.7', ] ); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -549,9 +548,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); @@ -565,7 +564,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.6', '127.0.0.7', ]); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -580,9 +579,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]); }); }); @@ -590,7 +589,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 ip', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -605,15 +604,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']]); }); it('will return 2 results if we have a list that excludes 2 ips', async () => { await importFile(supertest, log, 'ip', ['127.0.0.1', '127.0.0.5'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -628,9 +627,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -645,7 +644,7 @@ export default ({ getService }: FtrProviderContext) => { ['127.0.0.1', '127.0.0.5', '127.0.0.8'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -660,9 +659,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -687,7 +686,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.7', ] ); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -702,9 +701,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], @@ -721,7 +720,7 @@ export default ({ getService }: FtrProviderContext) => { '127.0.0.6', '127.0.0.7', ]); - const rule = getRuleForSignalTesting(['ip_as_array']); + const rule = getRuleForAlertTesting(['ip_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -736,9 +735,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort(); expect(ips).to.eql([ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/text_array.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/text_array.ts index fe4a13fcc3c84..915d353281f66 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/text_array.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type text', () => { + describe('@serverless @ess Rule exception operators for data type text', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/text_as_array'); }); @@ -43,7 +42,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -56,12 +55,12 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the text from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -71,7 +70,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 1 single text if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -83,9 +82,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -94,7 +93,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 text if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -114,14 +113,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 text if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -149,16 +148,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -170,13 +169,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -188,14 +187,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 0 results if we exclude two text', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -215,15 +214,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single text if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -235,9 +234,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -246,7 +245,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 text if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -258,14 +257,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 text if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -277,16 +276,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -298,13 +297,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -316,9 +315,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], ['word one', 'word two', 'word three', 'word four'], @@ -328,7 +327,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 1 results if matching against text for the empty array', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -339,16 +338,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 3 results if matching against text', async () => { - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -359,9 +358,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], ['word five', null, 'word six', 'word seven'], @@ -374,7 +373,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have two lists with an AND contradiction text === "word one" AND text === "word five"', async () => { await importFile(supertest, log, 'text', ['word one'], 'list_items_1.txt'); await importFile(supertest, log, 'text', ['word five'], 'list_items_2.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -398,9 +397,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -412,7 +411,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have two lists with an AND text === "word one" AND text === "word two" since we have an array', async () => { await importFile(supertest, log, 'text', ['word one'], 'list_items_1.txt'); await importFile(supertest, log, 'text', ['word two'], 'list_items_2.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -436,9 +435,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -448,7 +447,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have a list that includes 1 text', async () => { await importFile(supertest, log, 'text', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -463,9 +462,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -475,7 +474,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 2 results if we have a list that includes 2 text', async () => { await importFile(supertest, log, 'text', ['word one', 'word six'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -490,9 +489,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); @@ -504,7 +503,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word five', 'word eight'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -519,9 +518,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits.flat(10)).to.eql([]); }); }); @@ -529,7 +528,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 text', async () => { await importFile(supertest, log, 'text', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -544,15 +543,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 1 result if we have a list that excludes 1 text but repeat 2 elements from the array in the list', async () => { await importFile(supertest, log, 'text', ['word one', 'word two'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -567,15 +566,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 2 results if we have a list that excludes 2 text', async () => { await importFile(supertest, log, 'text', ['word one', 'word five'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -590,9 +589,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], ['word one', 'word two', 'word three', 'word four'], @@ -607,7 +606,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word six', 'word ten'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text_as_array']); + const rule = getRuleForAlertTesting(['text_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -622,9 +621,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], ['word five', null, 'word six', 'word seven'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts similarity index 58% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group8/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts index 2430b8f2148d9..b968919430064 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts @@ -7,12 +7,17 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine ESS/ Exception Operators Data Types API keyword_text_long Integration Tests', + }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts new file mode 100644 index 0000000000000..daf867a3a6839 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createTestConfig } from '../../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine Serverless/ Exception Operators Data Types keyword_text_long API Integration Tests', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/index.ts similarity index 82% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/index.ts index cf87276aac3ae..ad0a5d847b0fd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/index.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { describe('Detection exceptions data types and operators', function () { loadTestFile(require.resolve('./keyword')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/keyword.ts similarity index 70% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/keyword.ts index 2487fbcb7ab44..11289a31b1242 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/keyword.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type keyword', () => { + describe('@serverless @ess Rule exception operators for data type keyword', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/keyword'); }); @@ -43,7 +42,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -56,17 +55,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter 1 single keyword if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -78,14 +77,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 keyword if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -105,14 +104,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -140,14 +139,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 keyword if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -183,15 +182,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -203,13 +202,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -221,14 +220,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one']); }); it('will return 0 results if we exclude two keyword', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -248,15 +247,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -268,14 +267,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 keyword if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -287,14 +286,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -306,14 +305,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 keyword if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -325,15 +324,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -345,13 +344,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -363,16 +362,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against keyword', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -383,15 +382,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against keyword', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -402,9 +401,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); }); @@ -413,7 +412,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have two lists with an AND contradiction keyword === "word one" AND keyword === "word two"', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items_1.txt'); await importFile(supertest, log, 'keyword', ['word two'], 'list_items_2.txt'); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -437,15 +436,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('will return 3 results if we have a list that includes 1 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -460,15 +459,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('will return 2 results if we have a list that includes 2 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one', 'word three'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -483,9 +482,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word two']); }); @@ -497,7 +496,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -512,8 +511,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); @@ -521,7 +520,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -536,15 +535,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one']); }); it('will return 2 results if we have a list that excludes 2 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one', 'word three'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -559,9 +558,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one', 'word three']); }); @@ -573,7 +572,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -588,16 +587,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); }); describe('"matches wildcard" operator', () => { it('should return 0 alerts if wildcard matches all words', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -610,13 +609,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('should return 1 alert if wildcard exceptions match one, two, and three', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -637,14 +636,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four']); }); it('should return 3 alerts if one is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -657,14 +656,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should return 4 alerts if the wildcard matches nothing', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -676,16 +675,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); }); describe('"does not match wildcard" operator', () => { it('should return 4 results if excluded wildcard matches all 4 words', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -698,14 +697,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter in 2 keywords if using a wildcard exception', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -718,14 +717,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word three', 'word two']); }); it('should return 1 alert if "word one" is excluded', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -738,14 +737,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql(['word one']); }); it('should return 0 alerts if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['keyword']); + const rule = getRuleForAlertTesting(['keyword']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -757,8 +756,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/keyword_array.ts similarity index 70% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/keyword_array.ts index ff3611b4ab583..58f41321e7a86 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/keyword_array.ts @@ -12,28 +12,28 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; + import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type keyword', () => { + describe('@serverless @ess Rule exception operators for data type keyword', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/keyword_as_array'); }); @@ -45,7 +45,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -58,12 +58,12 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -73,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 1 single keyword if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -85,9 +85,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -96,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 keyword if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -116,14 +116,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -151,16 +151,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -172,13 +172,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -190,14 +190,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 0 results if we exclude two keyword', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -217,15 +217,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -237,9 +237,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -248,7 +248,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 keyword if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -260,14 +260,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -279,16 +279,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -300,13 +300,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -318,9 +318,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], ['word one', 'word two', 'word three', 'word four'], @@ -330,7 +330,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"exists" operator', () => { it('will return 1 results if matching against keyword for the empty array', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -341,16 +341,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 3 results if matching against keyword', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -361,9 +361,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], ['word five', null, 'word six', 'word seven'], @@ -376,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 4 results if we have two lists with an AND contradiction keyword === "word one" AND keyword === "word five"', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items_1.txt'); await importFile(supertest, log, 'keyword', ['word five'], 'list_items_2.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -400,9 +400,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -414,7 +414,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have two lists with an AND keyword === "word one" AND keyword === "word two" since we have an array', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items_1.txt'); await importFile(supertest, log, 'keyword', ['word two'], 'list_items_2.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -438,9 +438,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -450,7 +450,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 3 results if we have a list that includes 1 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -465,9 +465,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -477,7 +477,7 @@ export default ({ getService }: FtrProviderContext) => { it('will return 2 results if we have a list that includes 2 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one', 'word six'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -492,9 +492,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]); }); @@ -506,7 +506,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word five', 'word eight'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -521,9 +521,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(10)).to.eql([]); }); }); @@ -531,7 +531,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is not in list" operator', () => { it('will return 1 result if we have a list that excludes 1 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -546,15 +546,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 1 result if we have a list that excludes 1 keyword but repeat 2 elements from the array in the list', async () => { await importFile(supertest, log, 'keyword', ['word one', 'word two'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -569,15 +569,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('will return 2 results if we have a list that excludes 2 keyword', async () => { await importFile(supertest, log, 'keyword', ['word one', 'word five'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -592,9 +592,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word five', null, 'word six', 'word seven'], ['word one', 'word two', 'word three', 'word four'], @@ -609,7 +609,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word six', 'word ten'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -624,9 +624,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], ['word five', null, 'word six', 'word seven'], @@ -637,7 +637,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"matches wildcard" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -649,9 +649,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ [], ['word eight', 'word nine', 'word ten'], @@ -660,7 +660,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 keyword if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -672,14 +672,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([[], ['word five', null, 'word six', 'word seven']]); }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -691,16 +691,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits.flat(10)).to.eql([]); }); }); describe('"does not match wildcard" operator', () => { it('should filter 1 single keyword if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -712,14 +712,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([['word one', 'word two', 'word three', 'word four']]); }); it('should filter 2 keyword if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -731,9 +731,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], ['word one', 'word two', 'word three', 'word four'], @@ -741,7 +741,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 3 keyword if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['keyword_as_array']); + const rule = getRuleForAlertTesting(['keyword_as_array']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -753,9 +753,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); expect(hits).to.eql([ ['word eight', 'word nine', 'word ten'], ['word five', null, 'word six', 'word seven'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/long.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/long.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/long.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/long.ts index 93005464a8fe7..69803854e9306 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/long.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/long.ts @@ -12,28 +12,27 @@ import { deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type long', () => { + describe('@serverless @ess Rule exception operators for data type long', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/long'); await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/long_as_string'); @@ -45,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -58,17 +57,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the long from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); it('should filter 1 single long if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -80,14 +79,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 long if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -107,14 +106,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 long if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -142,14 +141,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 long if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -185,15 +184,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -205,13 +204,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -223,14 +222,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1']); }); it('will return 0 results if we exclude two long', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -250,15 +249,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); }); describe('"is one of" operator', () => { it('should filter 1 single long if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -270,14 +269,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('should filter 2 long if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -289,14 +288,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['3', '4']); }); it('should filter 3 long if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -308,14 +307,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['4']); }); it('should filter 4 long if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -327,15 +326,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -347,13 +346,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -365,16 +364,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '4']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against long', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -385,15 +384,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against long', async () => { - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -404,9 +403,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); }); @@ -415,7 +414,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against long values in the data set', () => { it('will return 3 results if we have a list that includes 1 long', async () => { await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -430,15 +429,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 long', async () => { await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -453,15 +452,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all long', async () => { await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -476,8 +475,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); }); @@ -485,7 +484,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 3 results if we have a list that includes 1 long', async () => { await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -500,15 +499,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '3', '4']); }); it('will return 2 results if we have a list that includes 2 long', async () => { await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -523,15 +522,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['2', '4']); }); it('will return 0 results if we have a list that includes all long', async () => { await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -546,8 +545,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql([]); }); @@ -557,7 +556,7 @@ export default ({ getService }: FtrProviderContext) => { '2', '3', ]); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -572,9 +571,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['4']); }); }); @@ -584,7 +583,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against long values in the data set', () => { it('will return 1 result if we have a list that excludes 1 long', async () => { await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -599,15 +598,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 long', async () => { await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -622,15 +621,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all long', async () => { await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long']); + const rule = getRuleForAlertTesting(['long']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -645,9 +644,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); }); @@ -655,7 +654,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against string values in the data set', () => { it('will return 1 result if we have a list that excludes 1 long', async () => { await importFile(supertest, log, 'long', ['1'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -670,15 +669,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1']); }); it('will return 2 results if we have a list that excludes 2 long', async () => { await importFile(supertest, log, 'long', ['1', '3'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -693,15 +692,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '3']); }); it('will return 4 results if we have a list that excludes all long', async () => { await importFile(supertest, log, 'long', ['1', '2', '3', '4'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -716,9 +715,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3', '4']); }); @@ -728,7 +727,7 @@ export default ({ getService }: FtrProviderContext) => { '2', '3', ]); - const rule = getRuleForSignalTesting(['long_as_string']); + const rule = getRuleForAlertTesting(['long_as_string']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -743,9 +742,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.long).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.long).sort(); expect(hits).to.eql(['1', '2', '3']); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/text.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/text.ts similarity index 71% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/text.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/text.ts index 81ccdcc8fa7f2..df7a42dc88de2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/text.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/text.ts @@ -13,28 +13,27 @@ import { deleteListsIndex, importFile, importTextFile, -} from '../../../../lists_api_integration/utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../../../../lists_api_integration/utils'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../../utils'; + waitForAlertsToBePresent, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('Rule exception operators for data type text', () => { + describe('@serverless @ess Rule exception operators for data type text', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/text'); await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces'); @@ -46,7 +45,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -59,17 +58,17 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should find all the text from the data set when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter 1 single text if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -81,14 +80,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 text if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -108,14 +107,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 text if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -143,14 +142,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 text if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -186,13 +185,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('should filter 1 single text using a single word', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -204,14 +203,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter all words using a common piece of text', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -223,13 +222,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('should filter 1 single text with punctuation added', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -241,16 +240,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); }); describe('"is not" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -262,13 +261,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just 1 result we excluded', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -280,14 +279,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); it('will return 0 results if we exclude two text', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -307,13 +306,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('should filter 1 single text using a single word', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -325,14 +324,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); it('should filter all words using a common piece of text', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -344,14 +343,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); it('should filter 1 single text with punctuation added', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -363,16 +362,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); }); describe('"is one of" operator', () => { it('should filter 1 single text if it is set as an exception', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -384,14 +383,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('should filter 2 text if both are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -403,14 +402,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three']); }); it('should filter 3 text if all 3 are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -422,14 +421,14 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four']); }); it('should filter 4 text if all are set as exceptions', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -441,15 +440,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); }); describe('"is not one of" operator', () => { it('will return 0 results if it cannot find what it is excluding', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -461,13 +460,13 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); it('will return just the result we excluded', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -479,16 +478,16 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one']); }); }); describe('"exists" operator', () => { it('will return 0 results if matching against text', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -499,15 +498,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); }); describe('"does not exist" operator', () => { it('will return 4 results if matching against text', async () => { - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -518,9 +517,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); }); @@ -529,7 +528,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against text values without spaces', () => { it('will return 3 results if we have a list that includes 1 text', async () => { await importFile(supertest, log, 'text', ['one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_no_spaces']); + const rule = getRuleForAlertTesting(['text_no_spaces']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -544,15 +543,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['four', 'three', 'two']); }); it('will return 2 results if we have a list that includes 2 text', async () => { await importFile(supertest, log, 'text', ['one', 'three'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_no_spaces']); + const rule = getRuleForAlertTesting(['text_no_spaces']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -567,9 +566,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['four', 'two']); }); @@ -581,7 +580,7 @@ export default ({ getService }: FtrProviderContext) => { ['one', 'two', 'three', 'four'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text_no_spaces']); + const rule = getRuleForAlertTesting(['text_no_spaces']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -596,8 +595,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); }); @@ -605,7 +604,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against text values with spaces', () => { it('will return 3 results if we have a list that includes 1 text', async () => { await importTextFile(supertest, log, 'text', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -620,9 +619,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); @@ -634,7 +633,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one additional wording'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -649,15 +648,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 3, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word three', 'word two']); }); it('will return 2 results if we have a list that includes 2 text', async () => { await importFile(supertest, log, 'text', ['word one', 'word three'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -672,9 +671,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word two']); }); @@ -686,7 +685,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -701,8 +700,8 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql([]); }); }); @@ -712,7 +711,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against text values without spaces', () => { it('will return 1 result if we have a list that excludes 1 text', async () => { await importTextFile(supertest, log, 'text', ['one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_no_spaces']); + const rule = getRuleForAlertTesting(['text_no_spaces']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -727,15 +726,15 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['one']); }); it('will return 2 results if we have a list that excludes 2 text', async () => { await importTextFile(supertest, log, 'text', ['one', 'three'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text_no_spaces']); + const rule = getRuleForAlertTesting(['text_no_spaces']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -750,9 +749,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['one', 'three']); }); @@ -764,7 +763,7 @@ export default ({ getService }: FtrProviderContext) => { ['one', 'two', 'three', 'four'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text_no_spaces']); + const rule = getRuleForAlertTesting(['text_no_spaces']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -779,9 +778,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['four', 'one', 'three', 'two']); }); }); @@ -789,7 +788,7 @@ export default ({ getService }: FtrProviderContext) => { describe('working against text values with spaces', () => { it('will return 1 result if we have a list that excludes 1 text', async () => { await importTextFile(supertest, log, 'text', ['word one'], 'list_items.txt'); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -804,9 +803,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); @@ -818,7 +817,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one additional wording'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -833,9 +832,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one']); }); @@ -847,7 +846,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word three'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -862,9 +861,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 2, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word one', 'word three']); }); @@ -876,7 +875,7 @@ export default ({ getService }: FtrProviderContext) => { ['word one', 'word two', 'word three', 'word four'], 'list_items.txt' ); - const rule = getRuleForSignalTesting(['text']); + const rule = getRuleForAlertTesting(['text']); const { id } = await createRuleWithExceptionEntries(supertest, log, rule, [ [ { @@ -891,9 +890,9 @@ export default ({ getService }: FtrProviderContext) => { ], ]); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsOpen = await getSignalsById(supertest, log, id); - const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort(); + await waitForAlertsToBePresent(supertest, log, 4, [id]); + const alertsOpen = await getAlertsById(supertest, log, id); + const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts similarity index 62% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group7/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts index 2430b8f2148d9..19391784466d3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group7/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts @@ -7,12 +7,16 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine ESS/ Exception Workflows API Integration Tests', + }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts new file mode 100644 index 0000000000000..1702c03591b64 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine Serverless/ Exception Workflows API Integration Tests', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/create_endpoint_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_endpoint_exceptions.ts similarity index 75% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group2/create_endpoint_exceptions.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_endpoint_exceptions.ts index a9ee0dc7c182c..1c647fe52810c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group2/create_endpoint_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_endpoint_exceptions.ts @@ -9,30 +9,24 @@ import { ToolingLog } from '@kbn/tooling-log'; import expect from 'expect'; import type SuperTest from 'supertest'; -import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; -import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; -import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; -import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; import { createRule, createRuleWithExceptionEntries, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, - getSignalsById, + getRuleForAlertTesting, + getAlertsById, waitForRuleSuccess, - waitForSignalsToBePresent, -} from '../../utils'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; + waitForAlertsToBePresent, +} from '../../../utils'; import { createListsIndex, deleteAllExceptions, deleteListsIndex, -} from '../../../lists_api_integration/utils'; +} from '../../../../../../lists_api_integration/utils'; + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; interface Host { os: { @@ -42,10 +36,10 @@ interface Host { } /** - * Convenience method to get signals by host and sort them for better deterministic testing + * Convenience method to get Alerts by host and sort them for better deterministic testing * since Elastic can return the hits back in any order we want to sort them on return for testing. * @param supertest Super test for testing. - * @param id The signals id + * @param id The Alerts id * @returns The array of hosts sorted */ export const getHostHits = async ( @@ -53,8 +47,8 @@ export const getHostHits = async ( log: ToolingLog, id: string ): Promise => { - const signalsOpen = await getSignalsById(supertest, log, id); - return signalsOpen.hits.hits + const AlertsOpen = await getAlertsById(supertest, log, id); + return AlertsOpen.hits.hits .map((hit) => hit._source?.host as Host) .sort((a, b) => { let sortOrder = 0; @@ -74,15 +68,13 @@ export const getHostHits = async ( }); }; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const es = getService('es'); - describe('create_endpoint_exceptions', () => { + describe('@serverless @ess create_endpoint_exceptions', () => { before(async () => { await esArchiver.load( 'x-pack/test/functional/es_archives/rule_exceptions/endpoint_without_host_type' @@ -98,7 +90,7 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createListsIndex(supertest, log); }); @@ -111,10 +103,10 @@ export default ({ getService }: FtrProviderContext) => { describe('no exceptions set', () => { it('should find all the "hosts" from a "agent" index when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); + await waitForAlertsToBePresent(supertest, log, 4, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -133,10 +125,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should find all the "hosts" from a "endpoint_without_host_type" index when no exceptions are set on the rule', async () => { - const rule = getRuleForSignalTesting(['endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['endpoint_without_host_type']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); + await waitForAlertsToBePresent(supertest, log, 4, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -158,7 +150,7 @@ export default ({ getService }: FtrProviderContext) => { describe('operating system types (os_types)', () => { describe('endpoints', () => { it('should filter 1 operating system types (os_type) if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -179,7 +171,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); + await waitForAlertsToBePresent(supertest, log, 3, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -195,7 +187,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 operating system types as an "OR" (os_type) if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -216,7 +208,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); + await waitForAlertsToBePresent(supertest, log, 3, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -232,7 +224,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter multiple operating system types if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -264,7 +256,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); + await waitForAlertsToBePresent(supertest, log, 2, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -277,7 +269,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter multiple operating system types (os_type) with multiple filter items for an endpoint', async () => { - const rule = getRuleForSignalTesting(['endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -309,7 +301,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); + await waitForAlertsToBePresent(supertest, log, 2, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -324,7 +316,7 @@ export default ({ getService }: FtrProviderContext) => { describe('agent', () => { it('should filter 1 operating system types (os_type) if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -345,7 +337,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); + await waitForAlertsToBePresent(supertest, log, 3, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -361,7 +353,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 1 operating system type as an "OR" (os_type) if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -382,7 +374,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); + await waitForAlertsToBePresent(supertest, log, 3, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -398,7 +390,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter multiple operating system types if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -430,7 +422,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); + await waitForAlertsToBePresent(supertest, log, 2, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -443,7 +435,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter multiple operating system types (os_type) with multiple filter items for an endpoint', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -475,7 +467,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); + await waitForAlertsToBePresent(supertest, log, 2, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -490,7 +482,7 @@ export default ({ getService }: FtrProviderContext) => { describe('agent and endpoint', () => { it('should filter 2 operating system types (os_type) if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -511,7 +503,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 6, [id]); + await waitForAlertsToBePresent(supertest, log, 6, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -536,7 +528,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 operating system types as an "OR" (os_type) if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -557,7 +549,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 6, [id]); + await waitForAlertsToBePresent(supertest, log, 6, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -582,7 +574,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter multiple operating system types if it is set as part of an endpoint exception', async () => { - const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -614,7 +606,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); + await waitForAlertsToBePresent(supertest, log, 4, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -633,7 +625,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter multiple operating system types (os_type) with multiple filter items for an endpoint', async () => { - const rule = getRuleForSignalTesting(['agent', 'endpoint_without_host_type']); + const rule = getRuleForAlertTesting(['agent', 'endpoint_without_host_type']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -665,7 +657,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); + await waitForAlertsToBePresent(supertest, log, 4, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -687,7 +679,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is" operator', () => { it('should filter 1 value set as an endpoint exception and 1 value set as a normal rule exception ', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -717,7 +709,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); + await waitForAlertsToBePresent(supertest, log, 1, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -727,7 +719,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 1 value set as an endpoint exception and 1 value set as a normal rule exception with os_type set', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -757,7 +749,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); + await waitForAlertsToBePresent(supertest, log, 1, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -769,7 +761,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"is one of" operator', () => { it('should filter 1 single value if it is set as an exception and the os_type is set to only 1 value', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -790,7 +782,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 3, [id]); + await waitForAlertsToBePresent(supertest, log, 3, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -806,7 +798,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 values if it is set as an exception and the os_type is set to 2 values', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -827,7 +819,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); + await waitForAlertsToBePresent(supertest, log, 2, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -840,7 +832,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter 2 values if it is set as an exception and the os_type is set to undefined', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -861,7 +853,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 2, [id]); + await waitForAlertsToBePresent(supertest, log, 2, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -874,7 +866,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should filter no values if they are set as an exception but the os_type is set to something not within the documents', async () => { - const rule = getRuleForSignalTesting(['agent']); + const rule = getRuleForAlertTesting(['agent']); const { id } = await createRuleWithExceptionEntries( supertest, log, @@ -895,7 +887,7 @@ export default ({ getService }: FtrProviderContext) => { ] ); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 4, [id]); + await waitForAlertsToBePresent(supertest, log, 4, [id]); const hits = await getHostHits(supertest, log, id); expect(hits).toEqual([ { @@ -913,113 +905,5 @@ export default ({ getService }: FtrProviderContext) => { ]); }); }); - describe('Add/edit exception comments by different users', () => { - const socManager = ROLES.soc_manager; - const detectionAdmin = ROLES.detections_admin; - - beforeEach(async () => { - await createUserAndRole(getService, detectionAdmin); - await createUserAndRole(getService, socManager); - }); - - afterEach(async () => { - await deleteUserAndRole(getService, detectionAdmin); - await deleteUserAndRole(getService, socManager); - await deleteAllExceptions(supertest, log); - }); - - it('Add comment on a new exception, add another comment has unicode from a different user', async () => { - await supertestWithoutAuth - .post(EXCEPTION_LIST_URL) - .auth(detectionAdmin, 'changeme') - .set('kbn-xsrf', 'true') - .send(getCreateExceptionListMinimalSchemaMock()) - .expect(200); - - // Add comment by the Detection Admin - await supertestWithoutAuth - .post(EXCEPTION_LIST_ITEM_URL) - .auth(detectionAdmin, 'changeme') - .set('kbn-xsrf', 'true') - .send({ - ...getCreateExceptionListItemMinimalSchemaMock(), - comments: [{ comment: 'Comment by user@detections_admin' }], - }) - .expect(200); - - const { body: items } = await supertestWithoutAuth - .get( - `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ - getCreateExceptionListMinimalSchemaMock().list_id - }` - ) - .auth(detectionAdmin, 'changeme') - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - // Validate the first user comment - expect(items.total).toEqual(1); - const [item] = items.data; - const detectionAdminComments = item.comments; - expect(detectionAdminComments.length).toEqual(1); - - expect(detectionAdminComments[0]).toEqual( - expect.objectContaining({ - created_by: 'detections_admin', - comment: 'Comment by user@detections_admin', - }) - ); - - const expectedId = item.id; - - // Update exception comment by different user Soc-manager - const { item_id: _, ...updateItemWithoutItemId } = - getUpdateMinimalExceptionListItemSchemaMock(); - - const updatePayload: UpdateExceptionListItemSchema = { - ...updateItemWithoutItemId, - comments: [ - ...(updateItemWithoutItemId.comments || []), - { comment: 'Comment by user@soc_manager' }, - ], - id: expectedId, - }; - await supertestWithoutAuth - .put(EXCEPTION_LIST_ITEM_URL) - .auth(socManager, 'changeme') - .set('kbn-xsrf', 'true') - .send(updatePayload) - .expect(200); - - const { body: itemsAfterUpdate } = await supertest - .get( - `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ - getCreateExceptionListMinimalSchemaMock().list_id - }` - ) - .auth(socManager, 'changeme') - .set('kbn-xsrf', 'true') - .send() - .expect(200); - const [itemAfterUpdate] = itemsAfterUpdate.data; - const detectionAdminAndSocManagerComments = itemAfterUpdate.comments; - - expect(detectionAdminAndSocManagerComments.length).toEqual(2); - - expect(detectionAdminAndSocManagerComments).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - created_by: 'detections_admin', - comment: 'Comment by user@detections_admin', - }), - expect.objectContaining({ - created_by: 'soc_manager', - comment: 'Comment by user@soc_manager', - }), - ]) - ); - }); - }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/rule_exception/create_rule_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts similarity index 98% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/rule_exception/create_rule_exceptions.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts index c48767b9a6586..8d78a0d7e48c4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/rule_exception/create_rule_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts @@ -15,8 +15,6 @@ import { ExceptionListTypeEnum, } from '@kbn/securitysolution-io-ts-list-types'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; - -import { FtrProviderContext } from '../../../../ftr_provider_context'; import { getRule, createRule, @@ -25,11 +23,12 @@ import { deleteAllRules, createExceptionList, deleteAllAlerts, -} from '../../utils'; +} from '../../../utils'; import { deleteAllExceptions, removeExceptionListItemServerGeneratedProperties, -} from '../../../../../lists_api_integration/utils'; +} from '../../../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; const getRuleExceptionItemMock = (): CreateRuleExceptionListItemSchema => ({ description: 'Exception item for rule default exception list', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/find_rule_exception_references.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/find_rule_exception_references.ts new file mode 100644 index 0000000000000..a2f996539f199 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/find_rule_exception_references.ts @@ -0,0 +1,230 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import expect from '@kbn/expect'; + +import { + CreateExceptionListSchema, + ExceptionListTypeEnum, +} from '@kbn/securitysolution-io-ts-list-types'; + +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL, + RuleReferencesSchema, +} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_exceptions'; + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +import { + createRule, + getSimpleRule, + deleteAllRules, + createExceptionList, + deleteAllAlerts, + createAlertsIndex, +} from '../../../utils'; +import { deleteAllExceptions } from '../../../../../../lists_api_integration/utils'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + + describe('@serverless @ess find_rule_exception_references', () => { + before(async () => { + await createAlertsIndex(supertest, log); + }); + + after(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + afterEach(async () => { + await deleteAllExceptions(supertest, log); + }); + + it('returns empty array per list_id if no references are found', async () => { + // create exception list + const newExceptionList: CreateExceptionListSchema = { + ...getCreateExceptionListMinimalSchemaMock(), + list_id: 'i_exist', + namespace_type: 'single', + type: ExceptionListTypeEnum.DETECTION, + }; + const exceptionList = await createExceptionList(supertest, log, newExceptionList); + + // create rule + await createRule(supertest, log, getSimpleRule('rule-1')); + + const { body: references } = await supertest + .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .query({ + ids: `${exceptionList.id}`, + list_ids: `${exceptionList.list_id}`, + namespace_types: `${exceptionList.namespace_type}`, + }) + .expect(200); + + const { + _version, + id, + created_at, + created_by, + tie_breaker_id, + updated_at, + updated_by, + ...referencesWithoutServerValues + } = references.references[0].i_exist; + + expect({ + references: [ + { + i_exist: { + ...referencesWithoutServerValues, + }, + }, + ], + }).to.eql({ + references: [ + { + i_exist: { + description: 'some description', + immutable: false, + list_id: 'i_exist', + name: 'some name', + namespace_type: 'single', + os_types: [], + tags: [], + type: 'detection', + version: 1, + referenced_rules: [], + }, + }, + ], + }); + }); + + it('returns empty array per list_id if list does not exist', async () => { + // create rule + await createRule(supertest, log, getSimpleRule('rule-1')); + + const { body: references } = await supertest + .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .query({ + ids: `1234`, + list_ids: `i_dont_exist`, + namespace_types: `single`, + }) + .expect(200); + + expect(references).to.eql({ references: [] }); + }); + + it('returns found references', async () => { + // create exception list + const newExceptionList: CreateExceptionListSchema = { + ...getCreateExceptionListMinimalSchemaMock(), + list_id: 'i_exist', + namespace_type: 'single', + type: ExceptionListTypeEnum.DETECTION, + }; + const exceptionList = await createExceptionList(supertest, log, newExceptionList); + const exceptionList2 = await createExceptionList(supertest, log, { + ...newExceptionList, + list_id: 'i_exist_2', + }); + + // create rule + await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + exceptions_list: [ + { + id: `${exceptionList.id}`, + list_id: `${exceptionList.list_id}`, + namespace_type: `${exceptionList.namespace_type}`, + type: `${exceptionList.type}`, + }, + { + id: `${exceptionList2.id}`, + list_id: `${exceptionList2.list_id}`, + namespace_type: `${exceptionList2.namespace_type}`, + type: `${exceptionList2.type}`, + }, + ], + }); + + const { body: references } = await supertest + .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .query({ + ids: `${exceptionList.id},${exceptionList2.id}`, + list_ids: `${exceptionList.list_id},${exceptionList2.list_id}`, + namespace_types: `${exceptionList.namespace_type},${exceptionList2.namespace_type}`, + }) + .expect(200); + + const refs = references.references.flatMap((ref: RuleReferencesSchema) => Object.keys(ref)); + + expect(refs.sort()).to.eql(['i_exist', 'i_exist_2'].sort()); + }); + + it('returns found references for all existing exception lists if no list id/list_id passed in', async () => { + // create exception list + const newExceptionList: CreateExceptionListSchema = { + ...getCreateExceptionListMinimalSchemaMock(), + list_id: 'i_exist', + namespace_type: 'single', + type: ExceptionListTypeEnum.DETECTION, + }; + const exceptionList = await createExceptionList(supertest, log, newExceptionList); + const exceptionList2 = await createExceptionList(supertest, log, { + ...newExceptionList, + list_id: 'i_exist_2', + }); + + // create rule + await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + exceptions_list: [ + { + id: `${exceptionList.id}`, + list_id: `${exceptionList.list_id}`, + namespace_type: `${exceptionList.namespace_type}`, + type: `${exceptionList.type}`, + }, + { + id: `${exceptionList2.id}`, + list_id: `${exceptionList2.list_id}`, + namespace_type: `${exceptionList2.namespace_type}`, + type: `${exceptionList2.type}`, + }, + ], + }); + + const { body: references } = await supertest + .get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .query({ + namespace_types: 'single,agnostic', + }) + .expect(200); + + const refs = references.references.flatMap((ref: RuleReferencesSchema) => Object.keys(ref)); + expect(refs.sort()).to.eql(['i_exist', 'i_exist_2', 'endpoint_list'].sort()); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/index.ts new file mode 100644 index 0000000000000..d42c92d7c09b5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Exceptions API', function () { + loadTestFile(require.resolve('./create_rule_exceptions')); + loadTestFile(require.resolve('./create_rule_exceptions')); + loadTestFile(require.resolve('./role_based_rule_exceptions_workflows')); + loadTestFile(require.resolve('./create_endpoint_exceptions')); + loadTestFile(require.resolve('./role_based_add_edit_comments')); + loadTestFile(require.resolve('./rule_exception_synchronizations')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_add_edit_comments.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_add_edit_comments.ts new file mode 100644 index 0000000000000..3ce0aa0bed874 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_add_edit_comments.ts @@ -0,0 +1,250 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import expect from 'expect'; + +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { deleteAllExceptions } from '../../../../../../lists_api_integration/utils'; +import { + createUserAndRole, + deleteUserAndRole, +} from '../../../../../../common/services/security_solution'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const log = getService('log'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('@serverless @ess @brokenInServerless role_based_add_edit_comments', () => { + const socManager = ROLES.soc_manager; + const detectionAdmin = ROLES.detections_admin; + + describe('Rule Exceptions', () => { + beforeEach(async () => { + await createUserAndRole(getService, detectionAdmin); + await createUserAndRole(getService, socManager); + }); + + afterEach(async () => { + await deleteUserAndRole(getService, detectionAdmin); + await deleteUserAndRole(getService, socManager); + await deleteAllExceptions(supertest, log); + }); + + it('Add comment on a new exception, add another comment has unicode from a different user', async () => { + await supertestWithoutAuth + .post(EXCEPTION_LIST_URL) + .auth(detectionAdmin, 'changeme') + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + const { os_types, ...ruleException } = getCreateExceptionListItemMinimalSchemaMock(); + + // Add comment by the Detection Admin + await supertestWithoutAuth + .post(EXCEPTION_LIST_ITEM_URL) + .auth(detectionAdmin, 'changeme') + .set('kbn-xsrf', 'true') + .send({ + ...ruleException, + comments: [{ comment: 'Comment by user@detections_admin' }], + }) + .expect(200); + + const { body: items } = await supertestWithoutAuth + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .auth(detectionAdmin, 'changeme') + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Validate the first user comment + expect(items.total).toEqual(1); + const [item] = items.data; + const detectionAdminComments = item.comments; + expect(detectionAdminComments.length).toEqual(1); + + expect(detectionAdminComments[0]).toEqual( + expect.objectContaining({ + created_by: 'detections_admin', + comment: 'Comment by user@detections_admin', + }) + ); + + const expectedId = item.id; + + // Update exception comment by different user Soc-manager + const { item_id: _, ...updateItemWithoutItemId } = + getUpdateMinimalExceptionListItemSchemaMock(); + + const updatePayload: UpdateExceptionListItemSchema = { + ...updateItemWithoutItemId, + comments: [ + ...(updateItemWithoutItemId.comments || []), + { comment: 'Comment by user@soc_manager' }, + ], + id: expectedId, + }; + await supertestWithoutAuth + .put(EXCEPTION_LIST_ITEM_URL) + .auth(socManager, 'changeme') + .set('kbn-xsrf', 'true') + .send(updatePayload) + .expect(200); + + const { body: itemsAfterUpdate } = await supertest + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .auth(socManager, 'changeme') + .set('kbn-xsrf', 'true') + .send() + .expect(200); + const [itemAfterUpdate] = itemsAfterUpdate.data; + const detectionAdminAndSocManagerComments = itemAfterUpdate.comments; + + expect(detectionAdminAndSocManagerComments.length).toEqual(2); + + expect(detectionAdminAndSocManagerComments).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + created_by: 'detections_admin', + comment: 'Comment by user@detections_admin', + }), + expect.objectContaining({ + created_by: 'soc_manager', + comment: 'Comment by user@soc_manager', + }), + ]) + ); + }); + }); + describe('Endpoint Exceptions', () => { + beforeEach(async () => { + await createUserAndRole(getService, detectionAdmin); + await createUserAndRole(getService, socManager); + }); + + afterEach(async () => { + await deleteUserAndRole(getService, detectionAdmin); + await deleteUserAndRole(getService, socManager); + await deleteAllExceptions(supertest, log); + }); + + it('Add comment on a new exception, add another comment has unicode from a different user', async () => { + await supertestWithoutAuth + .post(EXCEPTION_LIST_URL) + .auth(detectionAdmin, 'changeme') + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // Add comment by the Detection Admin + await supertestWithoutAuth + .post(EXCEPTION_LIST_ITEM_URL) + .auth(detectionAdmin, 'changeme') + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + comments: [{ comment: 'Comment by user@detections_admin' }], + }) + .expect(200); + + const { body: items } = await supertestWithoutAuth + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .auth(detectionAdmin, 'changeme') + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Validate the first user comment + expect(items.total).toEqual(1); + const [item] = items.data; + const detectionAdminComments = item.comments; + expect(detectionAdminComments.length).toEqual(1); + + expect(detectionAdminComments[0]).toEqual( + expect.objectContaining({ + created_by: 'detections_admin', + comment: 'Comment by user@detections_admin', + }) + ); + + const expectedId = item.id; + + // Update exception comment by different user Soc-manager + const { item_id: _, ...updateItemWithoutItemId } = + getUpdateMinimalExceptionListItemSchemaMock(); + + const updatePayload: UpdateExceptionListItemSchema = { + ...updateItemWithoutItemId, + comments: [ + ...(updateItemWithoutItemId.comments || []), + { comment: 'Comment by user@soc_manager' }, + ], + id: expectedId, + }; + await supertestWithoutAuth + .put(EXCEPTION_LIST_ITEM_URL) + .auth(socManager, 'changeme') + .set('kbn-xsrf', 'true') + .send(updatePayload) + .expect(200); + + const { body: itemsAfterUpdate } = await supertest + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .auth(socManager, 'changeme') + .set('kbn-xsrf', 'true') + .send() + .expect(200); + const [itemAfterUpdate] = itemsAfterUpdate.data; + const detectionAdminAndSocManagerComments = itemAfterUpdate.comments; + + expect(detectionAdminAndSocManagerComments.length).toEqual(2); + + expect(detectionAdminAndSocManagerComments).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + created_by: 'detections_admin', + comment: 'Comment by user@detections_admin', + }), + expect.objectContaining({ + created_by: 'soc_manager', + comment: 'Comment by user@soc_manager', + }), + ]) + ); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/exceptions_workflows.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts similarity index 72% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group3/exceptions_workflows.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts index d2a471174c304..a83aa609949ca 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/exceptions_workflows.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts @@ -8,10 +8,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import expect from 'expect'; -import type { - CreateExceptionListItemSchema, - UpdateExceptionListItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; +import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL, @@ -25,68 +22,76 @@ import type { ThresholdRuleCreateProps, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; -import { - getCreateExceptionListDetectionSchemaMock, - getCreateExceptionListMinimalSchemaMock, -} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; -import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; + +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; import { - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, + createAlertsIndex, + getRule, + createRule, getSimpleRule, + deleteAllRules, + createExceptionList, + createExceptionListItem, + getThresholdRuleForAlertTesting, getSimpleRuleOutput, removeServerGeneratedProperties, downgradeImmutableRule, - createRule, waitForRuleSuccess, installMockPrebuiltRules, - getRule, - createExceptionList, - createExceptionListItem, - waitForSignalsToBePresent, - getSignalsByIds, + waitForAlertsToBePresent, + getAlertsByIds, findImmutableRuleById, getPrebuiltRulesAndTimelinesStatus, - getOpenSignals, + getOpenAlerts, createRuleWithExceptionEntries, - getEqlRuleForSignalTesting, - getThresholdRuleForSignalTesting, -} from '../../utils'; + getEqlRuleForAlertTesting, + SAMPLE_PREBUILT_RULES, + deleteAllAlerts, + updateUsername, +} from '../../../utils'; + import { createListsIndex, deleteAllExceptions, deleteListsIndex, importFile, -} from '../../../lists_api_integration/utils'; -import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; -import { SAMPLE_PREBUILT_RULES } from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; +} from '../../../../../../lists_api_integration/utils'; +import { + createUserAndRole, + deleteUserAndRole, +} from '../../../../../../common/services/security_solution'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - - describe('create_rules_with_exceptions', () => { + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@serverless @ess role_based_rule_exceptions_workflows', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load(path); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload(path); }); describe('creating rules with exceptions', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -116,10 +121,10 @@ export default ({ getService }: FtrProviderContext) => { }, ], }; - + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); const rule = await createRule(supertest, log, ruleWithException); const expected = { - ...getSimpleRuleOutput(), + ...expectedRule, exceptions_list: [ { id, @@ -158,9 +163,10 @@ export default ({ getService }: FtrProviderContext) => { const rule = await createRule(supertest, log, ruleWithException); await waitForRuleSuccess({ supertest, log, id: rule.id }); const bodyToCompare = removeServerGeneratedProperties(rule); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); const expected = { - ...getSimpleRuleOutput(), + ...expectedRule, enabled: true, exceptions_list: [ { @@ -497,7 +503,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('t1_analyst', () => { + describe('@brokenInServerless t1_analyst', () => { const role = ROLES.t1_analyst; beforeEach(async () => { @@ -529,15 +535,15 @@ export default ({ getService }: FtrProviderContext) => { describe('tests with auditbeat data', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load(path); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload(path); }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -546,7 +552,7 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllExceptions(supertest, log); }); - it('should be able to execute against an exception list that does not include valid entries and get back 10 signals', async () => { + it('should be able to execute against an exception list that does not include valid entries and get back 10 alerts', async () => { const { id, list_id, namespace_type, type } = await createExceptionList( supertest, log, @@ -588,12 +594,12 @@ export default ({ getService }: FtrProviderContext) => { }; const { id: createdId } = await createRule(supertest, log, ruleWithException); await waitForRuleSuccess({ supertest, log, id: createdId }); - await waitForSignalsToBePresent(supertest, log, 10, [createdId]); - const signalsOpen = await getSignalsByIds(supertest, log, [createdId]); - expect(signalsOpen.hits.hits.length).toEqual(10); + await waitForAlertsToBePresent(supertest, log, 10, [createdId]); + const alertsOpen = await getAlertsByIds(supertest, log, [createdId]); + expect(alertsOpen.hits.hits.length).toEqual(10); }); - it('should be able to execute against an exception list that does include valid entries and get back 0 signals', async () => { + it('should be able to execute against an exception list that does include valid entries and get back 0 alerts', async () => { const rule: QueryRuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', @@ -616,11 +622,11 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); - it('should be able to execute against an exception list that does include valid case sensitive entries and get back 0 signals', async () => { + it('should be able to execute against an exception list that does include valid case sensitive entries and get back 0 alerts', async () => { const rule: QueryRuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', @@ -665,19 +671,19 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - const signalsOpen2 = await getOpenSignals(supertest, log, es, createdRule2); - // Expect signals here because all values are "Ubuntu" + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + const alertsOpen2 = await getOpenAlerts(supertest, log, es, createdRule2); + // Expect alerts here because all values are "Ubuntu" // and exception is one of ["ubuntu"] - expect(signalsOpen.hits.hits.length).toEqual(10); - // Expect no signals here because all values are "Ubuntu" + expect(alertsOpen.hits.hits.length).toEqual(10); + // Expect no alerts here because all values are "Ubuntu" // and exception is one of ["ubuntu", "Ubuntu"] - expect(signalsOpen2.hits.hits.length).toEqual(0); + expect(alertsOpen2.hits.hits.length).toEqual(0); }); - it('generates no signals when an exception is added for an EQL rule', async () => { + it('generates no alerts when an exception is added for an EQL rule', async () => { const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), + ...getEqlRuleForAlertTesting(['auditbeat-*']), query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', }; const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ @@ -690,13 +696,13 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); - it('generates no signals when an exception is added for a threshold rule', async () => { + it('generates no alerts when an exception is added for a threshold rule', async () => { const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), + ...getThresholdRuleForAlertTesting(['auditbeat-*']), threshold: { field: 'host.id', value: 700, @@ -712,11 +718,11 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); - it('generates no signals when an exception is added for a threat match rule', async () => { + it('generates no alerts when an exception is added for a threat match rule', async () => { const rule: ThreatMatchRuleCreateProps = { description: 'Detecting root and admin users', name: 'Query with a rule id', @@ -755,8 +761,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); describe('rules with value list exceptions', () => { beforeEach(async () => { @@ -767,7 +773,7 @@ export default ({ getService }: FtrProviderContext) => { await deleteListsIndex(supertest, log); }); - it('generates no signals when a value list exception is added for a query rule', async () => { + it('generates no alerts when a value list exception is added for a query rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); const rule: QueryRuleCreateProps = { @@ -795,11 +801,11 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); - it('generates no signals when a value list exception is added for a threat match rule', async () => { + it('generates no alerts when a value list exception is added for a threat match rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: ThreatMatchRuleCreateProps = { @@ -843,11 +849,11 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); - it('generates no signals when a value list exception is added for a threshold rule', async () => { + it('generates no alerts when a value list exception is added for a threshold rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: ThresholdRuleCreateProps = { @@ -880,15 +886,15 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); - it('generates no signals when a value list exception is added for an EQL rule', async () => { + it('generates no alerts when a value list exception is added for an EQL rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), + ...getEqlRuleForAlertTesting(['auditbeat-*']), query: 'configuration where host.name=="zeek-sensor-amsterdam"', }; @@ -905,8 +911,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toEqual(0); + const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule); + expect(alertsOpen.hits.hits.length).toEqual(0); }); it('should Not allow deleting value list when there are references and ignoreReferences is false', async () => { const valueListId = 'value-list-id'; @@ -944,252 +950,5 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); - describe('Synchronizations', () => { - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - await deleteAllExceptions(supertest, log); - }); - /* - This test to mimic if we have two browser tabs, and the user tried to - edit an exception in a tab after deleting it in another - */ - it('should Not edit an exception after being deleted', async () => { - const { list_id: skippedListId, ...newExceptionItem } = - getCreateExceptionListDetectionSchemaMock(); - const { - body: { id, list_id, namespace_type, type }, - } = await supertest - .post(EXCEPTION_LIST_URL) - .set('kbn-xsrf', 'true') - .send(newExceptionItem) - .expect(200); - - const ruleWithException: RuleCreateProps = { - ...getSimpleRule(), - exceptions_list: [ - { - id, - list_id, - namespace_type, - type, - }, - ], - }; - - await createRule(supertest, log, ruleWithException); - - // Delete the exception - await supertest - .delete(`${EXCEPTION_LIST_ITEM_URL}?id=${id}&namespace_type=single`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - // Edit after delete as if it was opened in another browser tab - const { body } = await supertest - .put(`${EXCEPTION_LIST_ITEM_URL}`) - .set('kbn-xsrf', 'true') - .send({ - id: list_id, - item_id: id, - name: 'edit', - entries: [{ field: 'ss', operator: 'included', type: 'match', value: 'ss' }], - namespace_type, - description: 'Exception list item - Edit', - type: 'simple', - }) - .expect(404); - - expect(body).toEqual({ - message: `exception list item id: "${list_id}" does not exist`, - status_code: 404, - }); - }); - /* - This test to mimic if we have two browser tabs, and the user tried to - edit an exception with value-list was deleted in another tab - */ - it('should Not allow editing an Exception with deleted ValueList', async () => { - await createListsIndex(supertest, log); - - const valueListId = 'value-list-id'; - await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); - const rule: QueryRuleCreateProps = { - ...getSimpleRule(), - query: 'host.name: "suricata-sensor-amsterdam"', - }; - const { exceptions_list: exceptionsList } = await createRuleWithExceptionEntries( - supertest, - log, - rule, - [ - [ - { - field: 'host.name', - operator: 'included', - type: 'list', - list: { - id: valueListId, - type: 'keyword', - }, - }, - ], - ] - ); - - const deleteReferences = false; - const ignoreReferences = true; - - const { id, list_id, namespace_type } = exceptionsList[0]; - - // Delete the value list - await supertest - .delete( - `${LIST_URL}?deleteReferences=${deleteReferences}&id=${valueListId}&ignoreReferences=${ignoreReferences}` - ) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - // edit the exception with the deleted value list - await supertest - .put(`${EXCEPTION_LIST_ITEM_URL}`) - .set('kbn-xsrf', 'true') - .send({ - id: list_id, - item_id: id, - name: 'edit', - entries: [ - { - field: 'host.name', - operator: 'included', - type: 'list', - list: { - id: valueListId, - type: 'keyword', - }, - }, - ], - namespace_type, - description: 'Exception list item - Edit', - type: 'simple', - }) - .expect(404); - - await deleteListsIndex(supertest, log); - }); - }); - - describe('Add/edit exception comments by different users', () => { - const socManager = ROLES.soc_manager; - const detectionAdmin = ROLES.detections_admin; - - beforeEach(async () => { - await createUserAndRole(getService, detectionAdmin); - await createUserAndRole(getService, socManager); - }); - - afterEach(async () => { - await deleteUserAndRole(getService, detectionAdmin); - await deleteUserAndRole(getService, socManager); - await deleteAllExceptions(supertest, log); - }); - - it('Add comment on a new exception, add another comment has unicode from a different user', async () => { - await supertestWithoutAuth - .post(EXCEPTION_LIST_URL) - .auth(detectionAdmin, 'changeme') - .set('kbn-xsrf', 'true') - .send(getCreateExceptionListDetectionSchemaMock()) - .expect(200); - - const { os_types, ...ruleException } = getCreateExceptionListItemMinimalSchemaMock(); - - // Add comment by the Detection Admin - await supertestWithoutAuth - .post(EXCEPTION_LIST_ITEM_URL) - .auth(detectionAdmin, 'changeme') - .set('kbn-xsrf', 'true') - .send({ - ...ruleException, - comments: [{ comment: 'Comment by user@detections_admin' }], - }) - .expect(200); - - const { body: items } = await supertestWithoutAuth - .get( - `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ - getCreateExceptionListMinimalSchemaMock().list_id - }` - ) - .auth(detectionAdmin, 'changeme') - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - // Validate the first user comment - expect(items.total).toEqual(1); - const [item] = items.data; - const detectionAdminComments = item.comments; - expect(detectionAdminComments.length).toEqual(1); - - expect(detectionAdminComments[0]).toEqual( - expect.objectContaining({ - created_by: 'detections_admin', - comment: 'Comment by user@detections_admin', - }) - ); - - const expectedId = item.id; - - // Update exception comment by different user Soc-manager - const { item_id: _, ...updateItemWithoutItemId } = - getUpdateMinimalExceptionListItemSchemaMock(); - - const updatePayload: UpdateExceptionListItemSchema = { - ...updateItemWithoutItemId, - comments: [ - ...(updateItemWithoutItemId.comments || []), - { comment: 'Comment by user@soc_manager' }, - ], - id: expectedId, - }; - await supertestWithoutAuth - .put(EXCEPTION_LIST_ITEM_URL) - .auth(socManager, 'changeme') - .set('kbn-xsrf', 'true') - .send(updatePayload) - .expect(200); - - const { body: itemsAfterUpdate } = await supertest - .get( - `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ - getCreateExceptionListMinimalSchemaMock().list_id - }` - ) - .auth(socManager, 'changeme') - .set('kbn-xsrf', 'true') - .send() - .expect(200); - const [itemAfterUpdate] = itemsAfterUpdate.data; - const detectionAdminAndSocManagerComments = itemAfterUpdate.comments; - - expect(detectionAdminAndSocManagerComments.length).toEqual(2); - - expect(detectionAdminAndSocManagerComments).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - created_by: 'detections_admin', - comment: 'Comment by user@detections_admin', - }), - expect.objectContaining({ - created_by: 'soc_manager', - comment: 'Comment by user@soc_manager', - }), - ]) - ); - }); - }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/rule_exception_synchronizations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/rule_exception_synchronizations.ts new file mode 100644 index 0000000000000..d89055f698ce6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/rule_exception_synchronizations.ts @@ -0,0 +1,177 @@ +/* + * 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. + */ +/* eslint-disable @typescript-eslint/naming-convention */ + +import expect from 'expect'; + +import { + EXCEPTION_LIST_ITEM_URL, + EXCEPTION_LIST_URL, + LIST_URL, +} from '@kbn/securitysolution-list-constants'; +import type { + QueryRuleCreateProps, + RuleCreateProps, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + deleteAllAlerts, + getSimpleRule, + createRuleWithExceptionEntries, + deleteAllRules, + createRule, +} from '../../../utils'; +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + + describe('@serverless @ess Synchronizations', () => { + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + await deleteAllExceptions(supertest, log); + }); + /* + This test to mimic if we have two browser tabs, and the user tried to + edit an exception in a tab after deleting it in another + */ + it('should Not edit an exception after being deleted', async () => { + const { list_id: skippedListId, ...newExceptionItem } = + getCreateExceptionListDetectionSchemaMock(); + const { + body: { id, list_id, namespace_type, type }, + } = await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(newExceptionItem) + .expect(200); + + const ruleWithException: RuleCreateProps = { + ...getSimpleRule(), + exceptions_list: [ + { + id, + list_id, + namespace_type, + type, + }, + ], + }; + + await createRule(supertest, log, ruleWithException); + + // Delete the exception + await supertest + .delete(`${EXCEPTION_LIST_ITEM_URL}?id=${id}&namespace_type=single`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Edit after delete as if it was opened in another browser tab + const { body } = await supertest + .put(`${EXCEPTION_LIST_ITEM_URL}`) + .set('kbn-xsrf', 'true') + .send({ + id: list_id, + item_id: id, + name: 'edit', + entries: [{ field: 'ss', operator: 'included', type: 'match', value: 'ss' }], + namespace_type, + description: 'Exception list item - Edit', + type: 'simple', + }) + .expect(404); + + expect(body).toEqual({ + message: `exception list item id: "${list_id}" does not exist`, + status_code: 404, + }); + }); + /* + This test to mimic if we have two browser tabs, and the user tried to + edit an exception with value-list was deleted in another tab + */ + it('should Not allow editing an Exception with deleted ValueList', async () => { + await createListsIndex(supertest, log); + + const valueListId = 'value-list-id'; + await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); + const rule: QueryRuleCreateProps = { + ...getSimpleRule(), + query: 'host.name: "suricata-sensor-amsterdam"', + }; + const { exceptions_list: exceptionsList } = await createRuleWithExceptionEntries( + supertest, + log, + rule, + [ + [ + { + field: 'host.name', + operator: 'included', + type: 'list', + list: { + id: valueListId, + type: 'keyword', + }, + }, + ], + ] + ); + + const deleteReferences = false; + const ignoreReferences = true; + + const { id, list_id, namespace_type } = exceptionsList[0]; + + // Delete the value list + await supertest + .delete( + `${LIST_URL}?deleteReferences=${deleteReferences}&id=${valueListId}&ignoreReferences=${ignoreReferences}` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // edit the exception with the deleted value list + await supertest + .put(`${EXCEPTION_LIST_ITEM_URL}`) + .set('kbn-xsrf', 'true') + .send({ + id: list_id, + item_id: id, + name: 'edit', + entries: [ + { + field: 'host.name', + operator: 'included', + type: 'list', + list: { + id: valueListId, + type: 'keyword', + }, + }, + ], + namespace_type, + description: 'Exception list item - Edit', + type: 'simple', + }) + .expect(404); + + await deleteListsIndex(supertest, log); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts similarity index 73% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/ess.config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts index 98564672f53c0..4fbad71828a44 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts @@ -9,14 +9,14 @@ import { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const functionalConfig = await readConfigFile( - require.resolve('../../config/ess/config.base.trial.ts') + require.resolve('../../../../../config/ess/config.base.trial') ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS API Integration Tests', + reportName: 'Detection Engine ESS/ Rule creation API Integration Tests', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts similarity index 60% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/serverless.config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts index 8e9cbe781e5aa..3c214b340ab74 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { createTestConfig } from '../../config/serverless/config.base'; +import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless API Integration Tests', + reportName: 'Detection Engine Serverless/ Rule creation API Integration Tests', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rule_creation/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts similarity index 99% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rule_creation/create_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts index 2cc5afa9f7340..297470d452d4e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rule_creation/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts @@ -17,7 +17,7 @@ import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detect import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { FtrProviderContext } from '../../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; import { createAlertsIndex, deleteAllRules, @@ -43,12 +43,12 @@ import { getActionsWithoutFrequencies, getSomeActionsWithFrequencies, updateUsername, -} from '../utils'; +} from '../../utils'; import { createUserAndRole, deleteUserAndRole, -} from '../../../../common/services/security_solution'; -import { EsArchivePathBuilder } from '../../../es_archive_path_builder'; +} from '../../../../../common/services/security_solution'; +import { EsArchivePathBuilder } from '../../../../es_archive_path_builder'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); @@ -56,6 +56,7 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const es = getService('es'); + // TODO: add a new service const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); const isServerless = config.get('serverless'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rule_creation/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/index.ts similarity index 85% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rule_creation/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/index.ts index e6069caf96ee0..49268f31ed9f9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rule_creation/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/index.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { FtrProviderContext } from '../../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Rule creation API', function () { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/index.ts deleted file mode 100644 index 171e6a095d8a5..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/exceptions/index.ts +++ /dev/null @@ -1,13 +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 ({ loadTestFile }: FtrProviderContext) { - describe('Exceptions API', function () { - loadTestFile(require.resolve('./rule_exception/create_rule_exceptions')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/index.ts deleted file mode 100644 index 1a507b5ffd8e1..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/index.ts +++ /dev/null @@ -1,15 +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 ({ loadTestFile }: FtrProviderContext) { - describe('Detections response API', function () { - loadTestFile(require.resolve('./exceptions')); - loadTestFile(require.resolve('./rule_creation')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/action/get_slack_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/get_slack_action.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/action/get_slack_action.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/get_slack_action.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/action/get_web_hook_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/get_web_hook_action.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/action/get_web_hook_action.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/get_web_hook_action.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts similarity index 50% rename from x-pack/test/security_solution_api_integration/test_suites/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts index 2f9c0eda7a61b..438d983a69e05 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts @@ -4,11 +4,6 @@ * 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 ({ loadTestFile }: FtrProviderContext) { - describe('Security solution API', function () { - loadTestFile(require.resolve('./detections_response')); - }); -} +export * from './get_slack_action'; +export * from './get_web_hook_action'; +export * from './remove_uuid_from_actions'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/action/remove_uuid_from_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/remove_uuid_from_actions.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/action/remove_uuid_from_actions.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/remove_uuid_from_actions.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/create_alerts_index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/create_alerts_index.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/create_alerts_index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/create_alerts_index.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/delete_all_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/delete_all_alerts.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/delete_all_alerts.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/delete_all_alerts.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_id.ts new file mode 100644 index 0000000000000..1a3f7e29c26c6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_id.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 type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { DETECTION_ENGINE_QUERY_SIGNALS_URL as DETECTION_ENGINE_QUERY_ALERTS_URL } from '@kbn/security-solution-plugin/common/constants'; +import { countDownTest } from '../count_down_test'; +import { getQueryAlertsId } from './get_query_alerts_ids'; + +/** + * Given a single rule id this will return only alerts based on that rule id. + * @param supertest agent + * @param ids Rule id + */ +export const getAlertsById = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + id: string +): Promise> => { + const alertsOpen = await countDownTest>( + async () => { + const response = await supertest + .post(DETECTION_ENGINE_QUERY_ALERTS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertsId([id])); + if (response.status !== 200) { + return { + passed: false, + returnValue: undefined, + }; + } else { + return { + passed: true, + returnValue: response.body, + }; + } + }, + 'getAlertsById', + log + ); + if (alertsOpen == null) { + throw new Error('Alerts not defined after countdown, cannot continue'); + } else { + return alertsOpen; + } +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/get_alerts_by_ids.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/get_alerts_by_ids.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts new file mode 100644 index 0000000000000..26e2459d7e2a3 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.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 type SuperTest from 'supertest'; +import type { Client } from '@elastic/elasticsearch'; +import type { ToolingLog } from '@kbn/tooling-log'; +import { + RuleExecutionStatus, + RuleExecutionStatusEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { waitForRuleStatus } from '../rules'; +import { refreshIndex } from '..'; +import { getAlertsByIds } from './get_alerts_by_ids'; + +export const getOpenAlerts = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + es: Client, + rule: RuleResponse, + status: RuleExecutionStatus = RuleExecutionStatusEnum.succeeded, + size?: number, + afterDate?: Date +) => { + await waitForRuleStatus(status, { supertest, log, id: rule.id, afterDate }); + // Critically important that we wait for rule success AND refresh the write index in that order before we + // assert that no Alerts were created. Otherwise, Alerts could be written but not available to query yet + // when we search, causing tests that check that Alerts are NOT created to pass when they should fail. + await refreshIndex(es, '.alerts-security.alerts-default*'); + return getAlertsByIds(supertest, log, [rule.id], size); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/get_query_alerts_ids.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_query_alerts_ids.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/get_query_alerts_ids.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_query_alerts_ids.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts new file mode 100644 index 0000000000000..0671df4d65c36 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +// TODO rename signal to alert +export * from './create_alerts_index'; +export * from './delete_all_alerts'; +export * from './wait_for_alert_to_complete'; +export * from './wait_for_alerts_to_be_present'; +export * from './wait_for_alert_to_complete'; +export * from './get_open_alerts'; +export * from './get_alerts_by_ids'; +export * from './get_query_alerts_ids'; +export * from './get_alerts_by_id'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/wait_for_alert_to_complete.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alert_to_complete.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/wait_for_alert_to_complete.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alert_to_complete.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/wait_for_alerts_to_be_present.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alert/wait_for_alerts_to_be_present.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts new file mode 100644 index 0000000000000..cfbcafbc06cb6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts @@ -0,0 +1,46 @@ +/* + * 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 { TransportResult } from '@elastic/elasticsearch'; +import type { ToolingLog } from '@kbn/tooling-log'; +import { countDownTest } from './count_down_test'; + +/** + * Does a plain countdown and checks against es queries for either conflicts in the error + * or for any over the wire issues such as timeouts or temp 404's to make the tests more + * reliant. + * @param esFunction The function to test against + * @param esFunctionName The name of the function to print if we encounter errors + * @param log The tooling logger + * @param retryCount The number of times to retry before giving up (has default) + * @param timeoutWait Time to wait before trying again (has default) + */ +export const countDownES = async ( + esFunction: () => Promise, unknown>>, + esFunctionName: string, + log: ToolingLog, + retryCount: number = 50, + timeoutWait = 250 +): Promise => { + await countDownTest( + async () => { + const result = await esFunction(); + if (result.body.version_conflicts !== 0) { + return { + passed: false, + errorMessage: 'Version conflicts for ${result.body.version_conflicts}', + }; + } else { + return { passed: true }; + } + }, + esFunctionName, + log, + retryCount, + timeoutWait + ); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/index.ts new file mode 100644 index 0000000000000..9fee06e191b86 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './list/create_exception_list'; +export * from './list/delete_exception_list'; +export * from './list/create_container_with_entries'; +export * from './list/create_container_with_endpoint_entries'; + +export * from './item/create_exception_list_item'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/item/create_exception_list_item.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/item/create_exception_list_item.ts new file mode 100644 index 0000000000000..fccbd3e243b17 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/item/create_exception_list_item.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 type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { + CreateExceptionListItemSchema, + ExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; + +import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; + +/** + * Helper to cut down on the noise in some of the tests. This checks for + * an expected 200 still and does not try to any retries. Creates exception lists + * @param supertest The supertest deps + * @param exceptionListItem The exception list item to create + * @param log The tooling logger + */ +export const createExceptionListItem = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + exceptionListItem: CreateExceptionListItemSchema +): Promise => { + const response = await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(exceptionListItem); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when creating an exception list item (createExceptionListItem). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts new file mode 100644 index 0000000000000..9c48f80019c4e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { + CreateExceptionListItemSchema, + ListArray, + NonEmptyEntriesArray, + OsTypeArray, +} from '@kbn/securitysolution-io-ts-list-types'; + +import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { createExceptionListItem } from '../item/create_exception_list_item'; +import { waitFor } from '../../wait_for'; +import { createExceptionList } from './create_exception_list'; + +/** + * Convenience testing function where you can pass in just the endpoint entries and you will + * get a container created with the entries. + * @param supertest super test agent + * @param endpointEntries The endpoint entries to create the rule and exception list from + * @param osTypes The os types to optionally add or not to add to the container + */ +export const createContainerWithEndpointEntries = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + endpointEntries: Array<{ + entries: NonEmptyEntriesArray; + osTypes: OsTypeArray | undefined; + }> +): Promise => { + // If not given any endpoint entries, return without any + if (endpointEntries.length === 0) { + return []; + } + + // create the endpoint exception list container + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, list_id, namespace_type, type } = await createExceptionList(supertest, log, { + description: 'endpoint description', + list_id: 'endpoint_list', + name: 'endpoint_list', + type: 'endpoint', + }); + + // Add the endpoint exception list container to the backend + await Promise.all( + endpointEntries.map((endpointEntry) => { + const exceptionListItem: CreateExceptionListItemSchema = { + description: 'endpoint description', + entries: endpointEntry.entries, + list_id: 'endpoint_list', + name: 'endpoint_list', + os_types: endpointEntry.osTypes, + type: 'simple', + }; + return createExceptionListItem(supertest, log, exceptionListItem); + }) + ); + + // To reduce the odds of in-determinism and/or bugs we ensure we have + // the same length of entries before continuing. + await waitFor( + async () => { + const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); + return body.data.length === endpointEntries.length; + }, + `within createContainerWithEndpointEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`, + log + ); + + return [ + { + id, + list_id, + namespace_type, + type, + }, + ]; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts new file mode 100644 index 0000000000000..dba2a1e1e3276 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { ListArray, NonEmptyEntriesArray } from '@kbn/securitysolution-io-ts-list-types'; + +import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { createExceptionList } from './create_exception_list'; +import { createExceptionListItem } from '../item/create_exception_list_item'; +import { waitFor } from '../../wait_for'; + +/** + * Convenience testing function where you can pass in just the endpoint entries and you will + * get a container created with the entries. + * @param supertest super test agent + * @param entries The entries to create the rule and exception list from + * @param osTypes The os types to optionally add or not to add to the container + */ +export const createContainerWithEntries = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + entries: NonEmptyEntriesArray[] +): Promise => { + // If not given any endpoint entries, return without any + if (entries.length === 0) { + return []; + } + // Create the rule exception list container + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, list_id, namespace_type, type } = await createExceptionList(supertest, log, { + description: 'some description', + list_id: 'some-list-id', + name: 'some name', + type: 'detection', + }); + + // Add the rule exception list container to the backend + await Promise.all( + entries.map((entry) => { + const exceptionListItem: CreateExceptionListItemSchema = { + description: 'some description', + list_id: 'some-list-id', + name: 'some name', + type: 'simple', + entries: entry, + }; + return createExceptionListItem(supertest, log, exceptionListItem); + }) + ); + + // To reduce the odds of in-determinism and/or bugs we ensure we have + // the same length of entries before continuing. + await waitFor( + async () => { + const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); + return body.data.length === entries.length; + }, + `within createContainerWithEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`, + log + ); + + return [ + { + id, + list_id, + namespace_type, + type, + }, + ]; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/exception_list/create_exception_list.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_exception_list.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/exception_list/create_exception_list.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_exception_list.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/exception_list/delete_exception_list.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/delete_exception_list.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/exception_list/delete_exception_list.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/delete_exception_list.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts index 15681d103d8a0..571ed891d22fd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts @@ -4,38 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export * from './rule/get_rule'; -export * from './rule/get_simple_rule'; -export * from './rule/create_rule'; -export * from './rule/delete_all_rules'; -export * from './rule/delete_rule'; -export * from './rule/get_simple_rule_output'; -export * from './rule/get_simple_rule_output_without_rule_id'; -export * from './rule/get_simple_rule_without_rule_id'; -export * from './rule/get_simple_rule_without_rule_id'; -export * from './rule/remove_server_generated_properties'; -export * from './rule/remove_server_generated_properties_including_rule_id'; -export * from './rule/get_simple_ml_rule'; -export * from './rule/get_simple_ml_rule_output'; -export * from './rule/wait_for_rule_status'; -export * from './rule/get_rule_for_alert_testing_with_timestamp_override'; -export * from './rule/get_rule_for_alert_testing'; -export * from './rule/get_threshold_rule_for_alert_testing'; -export * from './rule/get_rule_actions'; - -export * from './exception_list_and_item/exception_list/create_exception_list'; -export * from './exception_list_and_item/exception_list/delete_exception_list'; - -// TODO rename signal to alert -export * from './alert/create_alerts_index'; -export * from './alert/delete_all_alerts'; -export * from './alert/wait_for_alert_to_complete'; -export * from './alert/wait_for_alerts_to_be_present'; -export * from './alert/wait_for_alert_to_complete'; - -export * from './action/get_slack_action'; -export * from './action/get_web_hook_action'; -export * from './action/remove_uuid_from_actions'; +export * from './rules'; +export * from './exception_list_and_item'; +export * from './alerts'; +export * from './actions'; export * from './count_down_test'; +export * from './count_down_es'; export * from './update_username'; +export * from './refresh_index'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/refresh_index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/refresh_index.ts new file mode 100644 index 0000000000000..f888216cb6eed --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/refresh_index.ts @@ -0,0 +1,19 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; + +/** + * Refresh an index, making changes available to search. + * Useful for tests where we want to ensure that a rule does NOT create alerts, e.g. testing exceptions. + * @param es The ElasticSearch handle + */ +export const refreshIndex = async (es: Client, index?: string) => { + await es.indices.refresh({ + index, + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/create_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/create_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts new file mode 100644 index 0000000000000..ea608c48e7b8b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; +import type { + RuleCreateProps, + RuleResponse, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + createContainerWithEntries, + createContainerWithEndpointEntries, +} from '../exception_list_and_item'; +import { createRule } from './create_rule'; + +/** + * Convenience testing function where you can pass in just the entries and you will + * get a rule created with the entries added to an exception list and exception list item + * all auto-created at once. + * @param supertest super test agent + * @param rule The rule to create and attach an exception list to + * @param entries The entries to create the rule and exception list from + * @param endpointEntries The endpoint entries to create the rule and exception list from + * @param osTypes The os types to optionally add or not to add to the container + */ +export const createRuleWithExceptionEntries = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + rule: RuleCreateProps, + entries: NonEmptyEntriesArray[], + endpointEntries?: Array<{ + entries: NonEmptyEntriesArray; + osTypes: OsTypeArray | undefined; + }> +): Promise => { + const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); + const maybeEndpointList = await createContainerWithEndpointEntries( + supertest, + log, + endpointEntries ?? [] + ); + + // create the rule but don't run it immediately as running it immediately can cause + // the rule to sometimes not filter correctly the first time with an exception list + // or other timing issues. Then afterwards wait for the rule to have succeeded before + // returning. + const ruleWithException: RuleCreateProps = { + ...rule, + enabled: false, + exceptions_list: [...maybeExceptionList, ...maybeEndpointList], + }; + const ruleResponse = await createRule(supertest, log, ruleWithException); + const response = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ rule_id: ruleResponse.rule_id, enabled: true }); + + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when patching a rule with exception entries (createRuleWithExceptionEntries). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return ruleResponse; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/delete_all_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_all_rules.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/delete_all_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_all_rules.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/delete_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_rule.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/delete_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/downgrade_immutable_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/downgrade_immutable_rule.ts new file mode 100644 index 0000000000000..fc81e2fc30cb4 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/downgrade_immutable_rule.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 type { ToolingLog } from '@kbn/tooling-log'; +import type { Client } from '@elastic/elasticsearch'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { countDownES } from '../count_down_es'; + +export const downgradeImmutableRule = async ( + es: Client, + log: ToolingLog, + ruleId: string +): Promise => { + return countDownES( + async () => { + return es.updateByQuery( + { + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + refresh: true, + wait_for_completion: true, + body: { + script: { + lang: 'painless', + source: 'ctx._source.alert.params.version--', + }, + query: { + term: { + 'alert.params.ruleId': ruleId, + }, + }, + }, + }, + { meta: true } + ); + }, + 'downgradeImmutableRule', + log + ); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/find_immutable_rule_by_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/find_immutable_rule_by_id.ts new file mode 100644 index 0000000000000..55e7375c48986 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/find_immutable_rule_by_id.ts @@ -0,0 +1,44 @@ +/* + * 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 { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; + +/** + * Helper to cut down on the noise in some of the tests. This + * uses the find API to get an immutable rule by id. + * @param supertest The supertest deps + */ +export const findImmutableRuleById = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + ruleId: string +): Promise<{ + page: number; + perPage: number; + total: number; + data: RuleResponse[]; +}> => { + const response = await supertest + .get( + `${DETECTION_ENGINE_RULES_URL}/_find?filter=alert.attributes.params.immutable: true AND alert.attributes.params.ruleId: "${ruleId}"` + ) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when finding an immutable rule by id (findImmutableRuleById). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts new file mode 100644 index 0000000000000..b8253e0f9afec --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EqlRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getRuleForAlertTesting } from './get_rule_for_alert_testing'; + +/** + * This is a typical alert testing rule that is easy for most basic testing of output of EQL alerts. + * It starts out in an enabled true state. The 'from' is set very far back to test the basics of alert + * creation for EQL and testing by getting all the alerts at once. + * @param ruleId The optional ruleId which is eql-rule by default. + * @param enabled Enables the rule on creation or not. Defaulted to true. + */ +export const getEqlRuleForAlertTesting = ( + index: string[], + ruleId = 'eql-rule', + enabled = true +): EqlRuleCreateProps => ({ + ...getRuleForAlertTesting(index, ruleId, enabled), + type: 'eql', + language: 'eql', + query: 'any where true', +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_actions.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule_actions.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_actions.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_for_alert_testing.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule_for_alert_testing.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_for_alert_testing.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule_for_alert_testing_with_timestamp_override.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_for_alert_testing_with_timestamp_override.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_rule_for_alert_testing_with_timestamp_override.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_for_alert_testing_with_timestamp_override.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_ml_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_ml_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_ml_rule_output.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_output.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_ml_rule_output.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_output.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule_output.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule_output.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule_output_without_rule_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output_without_rule_id.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule_output_without_rule_id.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output_without_rule_id.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule_without_rule_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_without_rule_id.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_simple_rule_without_rule_id.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_without_rule_id.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_threshold_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threshold_rule_for_alert_testing.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/get_threshold_rule_for_alert_testing.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threshold_rule_for_alert_testing.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts new file mode 100644 index 0000000000000..ba91dea27743e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './get_rule'; +export * from './get_simple_rule'; +export * from './create_rule'; +export * from './delete_all_rules'; +export * from './delete_rule'; +export * from './get_simple_rule_output'; +export * from './get_simple_rule_output_without_rule_id'; +export * from './get_simple_rule_without_rule_id'; +export * from './get_simple_rule_without_rule_id'; +export * from './remove_server_generated_properties'; +export * from './remove_server_generated_properties_including_rule_id'; +export * from './get_simple_ml_rule'; +export * from './get_simple_ml_rule_output'; +export * from './wait_for_rule_status'; +export * from './get_rule_for_alert_testing_with_timestamp_override'; +export * from './get_rule_for_alert_testing'; +export * from './get_threshold_rule_for_alert_testing'; +export * from './get_rule_actions'; +export * from './find_immutable_rule_by_id'; +export * from './create_rule_with_exception_entries'; +export * from './downgrade_immutable_rule'; +export * from './get_eql_rule_for_alert_testing'; + +export * from './prebuilt_rules'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts new file mode 100644 index 0000000000000..0b4bfd9254b15 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts @@ -0,0 +1,113 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; +import { + getPrebuiltRuleMock, + getPrebuiltRuleWithExceptionsMock, +} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; +import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; + +/** + * A helper function to create a rule asset saved object + * + * @param overrideParams Params to override the default mock + * @returns Created rule asset saved object + */ +export const createRuleAssetSavedObject = (overrideParams: Partial) => ({ + 'security-rule': { + ...getPrebuiltRuleMock(), + ...overrideParams, + }, + type: 'security-rule', + references: [], + coreMigrationVersion: '8.6.0', + updated_at: '2022-11-01T12:56:39.717Z', + created_at: '2022-11-01T12:56:39.717Z', +}); + +export const SAMPLE_PREBUILT_RULES = [ + createRuleAssetSavedObject({ + ...getPrebuiltRuleWithExceptionsMock(), + rule_id: ELASTIC_SECURITY_RULE_ID, + tags: ['test-tag-1'], + enabled: true, + }), + createRuleAssetSavedObject({ + rule_id: '000047bb-b27a-47ec-8b62-ef1a5d2c9e19', + tags: ['test-tag-2'], + }), + createRuleAssetSavedObject({ + rule_id: '00140285-b827-4aee-aa09-8113f58a08f3', + tags: ['test-tag-3'], + }), +]; + +export const SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS = [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), +]; + +/** + * Creates saved objects with prebuilt rule assets which can be used for + * installing actual prebuilt rules after that. It creates saved objects with + * only latest versions of the rules. Tha matches the behavior of a rules + * package without historical versions. + * + * NOTE: Version is not added to the rule asset saved object id. + * + * @param es Elasticsearch client + */ +export const createPrebuiltRuleAssetSavedObjects = async ( + es: Client, + rules = SAMPLE_PREBUILT_RULES +): Promise => { + await es.bulk({ + refresh: true, + body: rules.flatMap((doc) => [ + { + index: { + _index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + _id: `security-rule:${doc['security-rule'].rule_id}`, + }, + }, + doc, + ]), + }); +}; + +/** + * Creates saved objects with prebuilt rule assets which can be used for + * installing actual prebuilt rules after that. It creates saved objects with + * historical versions of the rules. + * + * NOTE: Version is added to the rule asset saved object id. + * + * @param es Elasticsearch client + */ +export const createHistoricalPrebuiltRuleAssetSavedObjects = async ( + es: Client, + rules = SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS +): Promise => { + await es.bulk({ + refresh: true, + body: rules.flatMap((doc) => [ + { + index: { + _index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + _id: `security-rule:${doc['security-rule'].rule_id}_${doc['security-rule'].version}`, + }, + }, + doc, + ]), + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts new file mode 100644 index 0000000000000..2d03e597dc5af --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts @@ -0,0 +1,31 @@ +/* + * 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 { + GetPrebuiltRulesAndTimelinesStatusResponse, + PREBUILT_RULES_STATUS_URL, +} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type SuperTest from 'supertest'; + +/** + * (LEGACY) + * Helper to retrieve the prebuilt rules status + * + * @param supertest The supertest deps + */ +export const getPrebuiltRulesAndTimelinesStatus = async ( + supertest: SuperTest.SuperTest +): Promise => { + const response = await supertest + .get(PREBUILT_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200); + + return response.body; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts new file mode 100644 index 0000000000000..9970b6b13eeec --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './create_prebuilt_rule_saved_objects'; +export * from './get_prebuilt_rules_and_timelines_status'; +export * from './install_mock_prebuilt_rules'; +export * from './install_prebuilt_rules_and_timelines'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_mock_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_mock_prebuilt_rules.ts new file mode 100644 index 0000000000000..0e15f416e1238 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_mock_prebuilt_rules.ts @@ -0,0 +1,28 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { InstallPrebuiltRulesAndTimelinesResponse } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type SuperTest from 'supertest'; +import { createPrebuiltRuleAssetSavedObjects } from './create_prebuilt_rule_saved_objects'; +import { installPrebuiltRulesAndTimelines } from './install_prebuilt_rules_and_timelines'; + +/** + * Creates prebuilt rule mocks and installs them + * + * @param supertest Supertest instance + * @param es Elasticsearch client + * @returns Install prebuilt rules response + */ +export const installMockPrebuiltRules = async ( + supertest: SuperTest.SuperTest, + es: Client +): Promise => { + // Ensure there are prebuilt rule saved objects before installing rules + await createPrebuiltRuleAssetSavedObjects(es); + return installPrebuiltRulesAndTimelines(es, supertest); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules_and_timelines.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules_and_timelines.ts new file mode 100644 index 0000000000000..776af6074e07e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules_and_timelines.ts @@ -0,0 +1,56 @@ +/* + * 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 { + InstallPrebuiltRulesAndTimelinesResponse, + PREBUILT_RULES_URL, +} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; +import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; + +/** + * (LEGACY) + * Installs all prebuilt rules and timelines available in Kibana. Rules are + * installed from the security-rule saved objects. + * This is a legacy endpoint and has been replaced by: + * POST /internal/detection_engine/prebuilt_rules/installation/_perform + * + * - No rules will be installed if there are no security-rule assets (e.g., the + * package is not installed or mocks are not created). + * + * - If some prebuilt rules are already installed, they will be upgraded in case + * there are newer versions of them in security-rule assets. + * + * @param supertest SuperTest instance + * @returns Install prebuilt rules response + */ +export const installPrebuiltRulesAndTimelines = async ( + es: Client, + supertest: SuperTest.SuperTest +): Promise => { + const response = await supertest + .put(PREBUILT_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200); + + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + + return response.body; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/remove_server_generated_properties.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/remove_server_generated_properties.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/remove_server_generated_properties_including_rule_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties_including_rule_id.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/remove_server_generated_properties_including_rule_id.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties_including_rule_id.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/wait_for_rule_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/wait_for_rule_status.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rule/wait_for_rule_status.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/wait_for_rule_status.ts diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index 81d57dd0617fb..cb46d96f11a7f 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -26,6 +26,7 @@ "@kbn/securitysolution-io-ts-alerting-types", "@kbn/tooling-log", "@kbn/rule-data-utils", - "@kbn/securitysolution-list-constants" + "@kbn/securitysolution-list-constants", + "@kbn/core-saved-objects-server" ] } From 848c21a8d21023da038555f712e2c808c4eff885 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 18 Oct 2023 14:54:45 +0200 Subject: [PATCH 37/49] [Synthetics] Unskip data retention tests (#169071) --- .../e2e/synthetics/journeys/data_retention.journey.ts | 2 +- x-pack/plugins/synthetics/e2e/synthetics/journeys/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/synthetics/e2e/synthetics/journeys/data_retention.journey.ts b/x-pack/plugins/synthetics/e2e/synthetics/journeys/data_retention.journey.ts index 9487fb62ccf95..1e0c00f42a92f 100644 --- a/x-pack/plugins/synthetics/e2e/synthetics/journeys/data_retention.journey.ts +++ b/x-pack/plugins/synthetics/e2e/synthetics/journeys/data_retention.journey.ts @@ -52,7 +52,7 @@ journey(`DataRetentionPage`, async ({ page, params }) => { const screenshotChecks = await page.textContent( `tr:has-text("Browser Screenshots") span:has-text("KB")` ); - expect(Number(allChecks?.split('KB')[0])).toBeGreaterThan(450); + expect(Number(allChecks?.split('KB')[0])).toBeGreaterThan(400); expect(Number(browserChecks?.split('KB')[0])).toBeGreaterThan(50); expect(Number(networkChecks?.split('KB')[0])).toBeGreaterThan(300); expect(Number(screenshotChecks?.split('KB')[0])).toBeGreaterThan(25); diff --git a/x-pack/plugins/synthetics/e2e/synthetics/journeys/index.ts b/x-pack/plugins/synthetics/e2e/synthetics/journeys/index.ts index 40418a55e8d8e..e38219a78ec95 100644 --- a/x-pack/plugins/synthetics/e2e/synthetics/journeys/index.ts +++ b/x-pack/plugins/synthetics/e2e/synthetics/journeys/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export * from './data_retention.journey'; export * from './project_api_keys.journey'; export * from './getting_started.journey'; export * from './add_monitor.journey'; @@ -19,7 +20,6 @@ export * from './global_parameters.journey'; export * from './detail_flyout'; // export * from './alert_rules/default_status_alert.journey'; export * from './test_now_mode.journey'; -// export * from './data_retention.journey'; export * from './monitor_details_page/monitor_summary.journey'; export * from './test_run_details.journey'; export * from './step_details.journey'; From 8252706f1d734e295fece42eb7033938cb95c006 Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Wed, 18 Oct 2023 09:12:08 -0400 Subject: [PATCH 38/49] [8.11] Fix pipeline name in generated ingest processors (#169163) ## Summary When creating an inference pipeline in Search, pipeline name references in the metadata of the generated pipeline definition are incorrect; the `ml.inference.` prefix is missing. This PR fixes that. There are no functional side effects, since the pipeline name only appears in metadata and failure messages generated during ingestion. Affected fields in the pipeline definition: - `inference.on_failure[].append.value.message` - `inference.on_failure[].append.value.pipeline` - `append.value.pipeline` Without fix: ![274963743-743cac19-2543-4005-984b-a47468d3039a](https://github.com/elastic/kibana/assets/14224983/d19cef8d-672c-4eda-90cc-7d67e155ffe1) With fix (definition / indexed document): ![Screenshot 2023-10-17 at 14 45 26](https://github.com/elastic/kibana/assets/14224983/e044482e-36ef-42ce-8383-07b31c98e46d) ![Screenshot 2023-10-17 at 14 47 54](https://github.com/elastic/kibana/assets/14224983/91204fa7-9148-4c47-a7b9-63688a3420ab) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../search_index/pipelines/ml_inference/ml_inference_logic.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts index 4d3747f79adc1..dd01db93bd689 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -15,6 +15,7 @@ import { generateMlInferencePipelineBody, getMlInferencePrefixedFieldName, getMlModelTypesForModelConfig, + ML_INFERENCE_PREFIX, parseMlInferenceParametersFromPipeline, } from '../../../../../../../common/ml_inference_pipeline'; import { Status } from '../../../../../../../common/types/api'; @@ -533,7 +534,7 @@ export const MLInferenceLogic = kea< return generateMlInferencePipelineBody({ model, - pipelineName: configuration.pipelineName, + pipelineName: `${ML_INFERENCE_PREFIX}${configuration.pipelineName}`, fieldMappings: configuration.fieldMappings ?? [], inferenceConfig: configuration.inferenceConfig, }); From c58238d989dd7d64b375b50e77dfcb856002fe94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:20:59 +0200 Subject: [PATCH 39/49] [Index Management] Remove old code (#168885) ## Summary This PR removes some leftover code from deleting the index details flyout in https://github.com/elastic/kibana/pull/165705 --- .../constants/detail_panel_tabs.ts | 12 --- .../public/application/constants/index.ts | 8 -- .../public/application/lib/ace.js | 75 ------------------- .../public/application/services/api.ts | 14 ---- .../public/application/services/index.ts | 1 - 5 files changed, 110 deletions(-) delete mode 100644 x-pack/plugins/index_management/public/application/constants/detail_panel_tabs.ts delete mode 100644 x-pack/plugins/index_management/public/application/lib/ace.js diff --git a/x-pack/plugins/index_management/public/application/constants/detail_panel_tabs.ts b/x-pack/plugins/index_management/public/application/constants/detail_panel_tabs.ts deleted file mode 100644 index 29000412d30e8..0000000000000 --- a/x-pack/plugins/index_management/public/application/constants/detail_panel_tabs.ts +++ /dev/null @@ -1,12 +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 const TAB_SUMMARY = 'TAB_SUMMARY'; -export const TAB_SETTINGS = 'TAB_SETTINGS'; -export const TAB_MAPPING = 'TAB_MAPPING'; -export const TAB_STATS = 'TAB_STATS'; -export const TAB_EDIT_SETTINGS = 'TAB_EDIT_SETTINGS'; diff --git a/x-pack/plugins/index_management/public/application/constants/index.ts b/x-pack/plugins/index_management/public/application/constants/index.ts index 7a1caf5e50771..939a5867a47df 100644 --- a/x-pack/plugins/index_management/public/application/constants/index.ts +++ b/x-pack/plugins/index_management/public/application/constants/index.ts @@ -7,14 +7,6 @@ export { REFRESH_RATE_INDEX_LIST } from './refresh_intervals'; -export { - TAB_SUMMARY, - TAB_SETTINGS, - TAB_MAPPING, - TAB_STATS, - TAB_EDIT_SETTINGS, -} from './detail_panel_tabs'; - export const REACT_ROOT_ID = 'indexManagementReactRoot'; export * from './ilm_locator'; diff --git a/x-pack/plugins/index_management/public/application/lib/ace.js b/x-pack/plugins/index_management/public/application/lib/ace.js deleted file mode 100644 index 3b6c6637215a3..0000000000000 --- a/x-pack/plugins/index_management/public/application/lib/ace.js +++ /dev/null @@ -1,75 +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 from 'brace'; -import 'brace/ext/language_tools'; - -const splitTokens = (line) => { - return line.split(/\s+/); -}; -const wordCompleter = (words) => { - return { - identifierRegexps: [ - /[a-zA-Z_0-9\.\$\-\u00A2-\uFFFF]/, // adds support for dot character - ], - getCompletions: (editor, session, pos, prefix, callback) => { - const document = session.getDocument(); - const currentLine = document.getLine(pos.row); - const previousLine = document.getLine(pos.row - 1); - const currentTokens = splitTokens(currentLine.slice(0, pos.column)); - const fullLineTokens = splitTokens(currentLine); - const isInArray = previousLine && splitTokens(previousLine).slice(-1)[0] === '['; - const [, secondToken = null] = currentTokens; - const [, secondFullToken = null] = fullLineTokens; - if (isInArray || currentTokens.length > 2) { - return callback(null, []); - } - const startQuote = secondToken === '"' ? '' : '"'; - const endQuote = secondFullToken === '""' ? '' : '"'; - callback( - null, - words.map((word) => { - return { - caption: ` ${word}`, - value: `${startQuote}${word}${endQuote}`, - }; - }) - ); - }, - }; -}; - -export const createAceEditor = (div, value, readOnly = true, autocompleteArray) => { - const editor = brace.edit(div); - editor.$blockScrolling = Infinity; - editor.setValue(value, -1); - const session = editor.getSession(); - session.setUseWrapMode(true); - session.setMode('ace/mode/json'); - if (autocompleteArray) { - const languageTools = brace.acequire('ace/ext/language_tools'); - const autocompleter = wordCompleter(autocompleteArray); - languageTools.setCompleters([autocompleter]); - } - const options = { - readOnly, - highlightActiveLine: false, - highlightGutterLine: false, - minLines: 20, - maxLines: 30, - }; - //done this way to avoid warnings about unrecognized options - const autocompleteOptions = readOnly - ? {} - : { - enableBasicAutocompletion: true, - enableLiveAutocompletion: true, - }; - editor.setOptions({ ...options, ...autocompleteOptions }); - editor.setBehavioursEnabled(!readOnly); - return editor; -}; diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 54391aaaedf43..30c8339840018 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -41,7 +41,6 @@ import { Index, IndexSettingsResponse, } from '../../../common'; -import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants'; import { useRequest, sendRequest } from './use_request'; import { httpService } from './http'; import { UiMetricService } from './ui_metric'; @@ -245,19 +244,6 @@ export async function loadIndexMapping(indexName: string) { return response; } -export async function loadIndexData(type: string, indexName: string) { - switch (type) { - case TAB_MAPPING: - return loadIndexMapping(indexName); - - case TAB_SETTINGS: - return loadIndexSettings(indexName); - - case TAB_STATS: - return loadIndexStats(indexName); - } -} - export function useLoadIndexTemplates() { return useRequest<{ templates: TemplateListItem[]; legacyTemplates: TemplateListItem[] }>({ path: `${API_BASE_PATH}/index_templates`, diff --git a/x-pack/plugins/index_management/public/application/services/index.ts b/x-pack/plugins/index_management/public/application/services/index.ts index 058a09d3f15d1..2a88ce37d9a99 100644 --- a/x-pack/plugins/index_management/public/application/services/index.ts +++ b/x-pack/plugins/index_management/public/application/services/index.ts @@ -20,7 +20,6 @@ export { updateIndexSettings, loadIndexStats, loadIndexMapping, - loadIndexData, useLoadIndexTemplates, simulateIndexTemplate, useLoadNodesPlugins, From 11b1bc77a6260d5f59b977607eae4a1f4ab63a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:22:54 +0200 Subject: [PATCH 40/49] [Index Management] Update editable index settings for Serverless (#168884) ## Summary Fixes https://github.com/elastic/kibana/issues/165895 This PR limits which index settings are displayed on the index details page, "Settings" tab in the edit mode. On serverless only a handful of index settings will be editable by the user. The UI only prevents displaying some index settings, but it's still possible for the user to type in a setting that can't be edited. That is the case on dedicated as well. ### How to test 1. Start Serverless ES and Kibana 2. Navigate to Index Management and create a test index 3. Click on the index name and on the details page click the tab "Settings" 4. Toggle the "Edit mode" switch and verify that only editable settings are displayed. #### Screenshot Screenshot 2023-10-16 at 20 25 49 --- config/serverless.yml | 2 + .../test_suites/core_plugins/rendering.ts | 1 + .../helpers/setup_environment.tsx | 2 +- .../index_details_page.test.tsx | 34 ++++++++++---- .../index_details_page/mocks.ts | 5 +- .../public/application/app_context.tsx | 1 + .../public/application/lib/edit_settings.ts | 20 +++++++- .../application/mount_management_section.ts | 14 ++---- .../details_page_settings_content.tsx | 46 ++++++++++++++----- .../plugins/index_management/public/plugin.ts | 11 +++-- .../plugins/index_management/public/types.ts | 1 + .../plugins/index_management/server/config.ts | 7 +++ 12 files changed, 108 insertions(+), 36 deletions(-) diff --git a/config/serverless.yml b/config/serverless.yml index 30c4aff914a51..97a7e638eb2d8 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -42,6 +42,8 @@ xpack.index_management.enableIndexActions: false xpack.index_management.enableLegacyTemplates: false # Disable index stats information from Index Management UI xpack.index_management.enableIndexStats: false +# Only limited index settings can be edited +xpack.index_management.editableIndexSettings: limited # Keep deeplinks visible so that they are shown in the sidenav dev_tools.deeplinks.navLinkStatus: visible diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 83ecf99f40196..88d354ff25a47 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -266,6 +266,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.index_management.enableIndexActions (any)', 'xpack.index_management.enableLegacyTemplates (any)', 'xpack.index_management.enableIndexStats (any)', + 'xpack.index_management.editableIndexSettings (any)', 'xpack.infra.sources.default.fields.message (array)', /** * Feature flags bellow are conditional based on traditional/serverless offering 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 20ae08038a2a2..5fb22ecd4d6e7 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 @@ -82,7 +82,7 @@ const appDependencies = { enableLegacyTemplates: true, enableIndexActions: true, enableIndexStats: true, - enableIndexDetailsPage: false, + editableIndexSettings: 'all', }, } as any; 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 ec5c3288043e1..26812d06b5d64 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 @@ -17,7 +17,8 @@ import { IndexManagementBreadcrumb, } from '../../../public/application/services/breadcrumbs'; import { - testIndexEditableSettings, + testIndexEditableSettingsAll, + testIndexEditableSettingsLimited, testIndexMappings, testIndexMock, testIndexName, @@ -429,13 +430,30 @@ describe('', () => { await testBed.actions.settings.clickEditModeSwitch(); }); - it('displays the editable settings (flattened and filtered)', () => { + it('displays all editable settings (flattened and filtered)', () => { const editorContent = testBed.actions.settings.getCodeEditorContent(); - expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettings, null, 2)); + expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettingsAll, null, 2)); + }); + + it('displays limited editable settings (flattened and filtered)', async () => { + await act(async () => { + testBed = await setup({ + httpSetup, + dependencies: { + config: { editableIndexSettings: 'limited' }, + }, + }); + }); + + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); + await testBed.actions.settings.clickEditModeSwitch(); + const editorContent = testBed.actions.settings.getCodeEditorContent(); + expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettingsLimited, null, 2)); }); it('updates the settings', async () => { - const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' }; await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); await testBed.actions.settings.saveSettings(); expect(httpSetup.put).toHaveBeenLastCalledWith( @@ -452,7 +470,7 @@ describe('', () => { it('reloads the settings after an update', async () => { const numberOfRequests = 2; expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); - const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' }; await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); await testBed.actions.settings.saveSettings(); expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); @@ -463,11 +481,11 @@ describe('', () => { }); it('resets the changes in the editor', async () => { - const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' }; await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); await testBed.actions.settings.resetChanges(); const editorContent = testBed.actions.settings.getCodeEditorContent(); - expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettings, null, 2)); + expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettingsAll, null, 2)); }); }); }); @@ -664,7 +682,7 @@ describe('', () => { it('updates settings with the encoded index name', async () => { await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings); await testBed.actions.settings.clickEditModeSwitch(); - const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' }; + const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' }; await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings)); await testBed.actions.settings.saveSettings(); expect(httpSetup.put).toHaveBeenLastCalledWith( diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts index 67ff8d57a1eb8..8adab61ec9a56 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts @@ -67,11 +67,14 @@ export const testIndexSettings = { }, }, }; -export const testIndexEditableSettings = { +export const testIndexEditableSettingsAll = { 'index.priority': '1', 'index.query.default_field': ['*'], 'index.routing.allocation.include._tier_preference': 'data_content', }; +export const testIndexEditableSettingsLimited = { + 'index.query.default_field': ['*'], +}; // Mocking partial index stats response export const testIndexStats = { 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 8ae5d95a8bca9..0f2a03f76011a 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -53,6 +53,7 @@ export interface AppDependencies { enableIndexActions: boolean; enableLegacyTemplates: boolean; enableIndexStats: boolean; + editableIndexSettings: 'all' | 'limited'; }; history: ScopedHistory; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; diff --git a/x-pack/plugins/index_management/public/application/lib/edit_settings.ts b/x-pack/plugins/index_management/public/application/lib/edit_settings.ts index 936555b8c47ec..16a67633224a9 100644 --- a/x-pack/plugins/index_management/public/application/lib/edit_settings.ts +++ b/x-pack/plugins/index_management/public/application/lib/edit_settings.ts @@ -22,7 +22,25 @@ export const readOnlySettings = [ 'index.routing_partition_size', 'index.store.type', ]; -export const settingsToDisplay = [ + +export const limitedEditableSettings = [ + 'index.blocks.write', + 'index.blocks.read', + 'index.blocks.read_only', + 'index.default_pipeline', + 'index.lifecycle.origination_date', + 'index.final_pipeline', + 'index.query.default_field', + 'index.refresh_interval', + 'index.mapping.ignore_malformed', + 'index.mapping.total_fields.limit', + 'index.merge.policy.deletes_pct_allowed', + 'index.merge.policy.max_merge_at_once', + 'index.merge.policy.expunge_deletes_allowed', + 'index.merge.policy.floor_segment', +]; + +export const defaultsToDisplay = [ 'index.number_of_replicas', 'index.blocks.read_only_allow_delete', 'index.codec', 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 d51c8527a6ef5..bd7d778ae103d 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 @@ -54,9 +54,7 @@ export async function mountManagementSection({ extensionsService, isFleetEnabled, kibanaVersion, - enableIndexActions = true, - enableLegacyTemplates = true, - enableIndexStats = true, + config, cloud, }: { coreSetup: CoreSetup; @@ -65,9 +63,7 @@ export async function mountManagementSection({ extensionsService: ExtensionsService; isFleetEnabled: boolean; kibanaVersion: SemVer; - enableIndexActions?: boolean; - enableLegacyTemplates?: boolean; - enableIndexStats?: boolean; + config: AppDependencies['config']; cloud?: CloudSetup; }) { const { element, setBreadcrumbs, history, theme$ } = params; @@ -114,11 +110,7 @@ export async function mountManagementSection({ uiMetricService, extensionsService, }, - config: { - enableIndexActions, - enableLegacyTemplates, - enableIndexStats, - }, + config, history, setBreadcrumbs, uiSettings, diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx index 37b10dc851a5a..035e647d19b11 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings_content.tsx @@ -30,22 +30,39 @@ import { Error } from '../../../../../shared_imports'; import { documentationService, updateIndexSettings } from '../../../../services'; import { notificationService } from '../../../../services/notification'; import { flattenObject } from '../../../../lib/flatten_object'; -import { readOnlySettings, settingsToDisplay } from '../../../../lib/edit_settings'; +import { + readOnlySettings, + defaultsToDisplay, + limitedEditableSettings, +} from '../../../../lib/edit_settings'; +import { AppDependencies, useAppContext } from '../../../../app_context'; -const getEditableSettings = ( - data: Props['data'], - isIndexOpen: boolean -): { originalSettings: Record; settingsString: string } => { +const getEditableSettings = ({ + data, + isIndexOpen, + editableIndexSettings, +}: { + data: Props['data']; + isIndexOpen: boolean; + editableIndexSettings: AppDependencies['config']['editableIndexSettings']; +}): { originalSettings: Record; settingsString: string } => { const { defaults, settings } = data; // settings user has actually set const flattenedSettings = flattenObject(settings); // settings with their defaults const flattenedDefaults = flattenObject(defaults); - const filteredDefaults = _.pick(flattenedDefaults, settingsToDisplay); - const newSettings = { ...filteredDefaults, ...flattenedSettings }; - // store these to be used as autocomplete values later - readOnlySettings.forEach((e) => delete newSettings[e]); - // can't change codec on open index + const filteredDefaults = _.pick(flattenedDefaults, defaultsToDisplay); + + let newSettings = { ...filteredDefaults, ...flattenedSettings }; + if (editableIndexSettings === 'limited') { + // only pick limited settings + newSettings = _.pick(newSettings, limitedEditableSettings); + } else { + // remove read only settings + readOnlySettings.forEach((e) => delete newSettings[e]); + } + + // can't change codec on an open index if (isIndexOpen) { delete newSettings['index.codec']; } @@ -67,12 +84,19 @@ export const DetailsPageSettingsContent: FunctionComponent = ({ reloadIndexSettings, }) => { const [isEditMode, setIsEditMode] = useState(false); + const { + config: { editableIndexSettings }, + } = useAppContext(); const onEditModeChange = (event: EuiSwitchEvent) => { setUpdateError(null); setIsEditMode(event.target.checked); }; - const { originalSettings, settingsString } = getEditableSettings(data, isIndexOpen); + const { originalSettings, settingsString } = getEditableSettings({ + data, + isIndexOpen, + editableIndexSettings, + }); const [editableSettings, setEditableSettings] = useState(settingsString); const [isLoading, setIsLoading] = useState(false); const [updateError, setUpdateError] = useState(null); diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index bfa2eab8a753c..8d043ab0af6bc 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -41,11 +41,18 @@ export class IndexMgmtUIPlugin { enableIndexActions, enableLegacyTemplates, enableIndexStats, + editableIndexSettings, } = this.ctx.config.get(); if (isIndexManagementUiEnabled) { const { fleet, usageCollection, management, cloud } = plugins; const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version); + const config = { + enableIndexActions: enableIndexActions ?? true, + enableLegacyTemplates: enableLegacyTemplates ?? true, + enableIndexStats: enableIndexStats ?? true, + editableIndexSettings: editableIndexSettings ?? 'all', + }; management.sections.section.data.registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), @@ -59,9 +66,7 @@ export class IndexMgmtUIPlugin { extensionsService: this.extensionsService, isFleetEnabled: Boolean(fleet), kibanaVersion, - enableIndexActions, - enableLegacyTemplates, - enableIndexStats, + config, cloud, }); }, diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 36e4f383e265d..3a1958c5e7e93 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -35,4 +35,5 @@ export interface ClientConfigType { enableIndexActions?: boolean; enableLegacyTemplates?: boolean; enableIndexStats?: boolean; + editableIndexSettings?: 'all' | 'limited'; } diff --git a/x-pack/plugins/index_management/server/config.ts b/x-pack/plugins/index_management/server/config.ts index a2b3c3259a2ed..e629669ed5311 100644 --- a/x-pack/plugins/index_management/server/config.ts +++ b/x-pack/plugins/index_management/server/config.ts @@ -41,6 +41,12 @@ const schemaLatest = schema.object( // We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana serverless: schema.boolean({ defaultValue: true }), }), + editableIndexSettings: offeringBasedSchema({ + // on serverless only a limited set of index settings can be edited + serverless: schema.oneOf([schema.literal('all'), schema.literal('limited')], { + defaultValue: 'all', + }), + }), }, { defaultValue: undefined } ); @@ -51,6 +57,7 @@ const configLatest: PluginConfigDescriptor = { enableIndexActions: true, enableLegacyTemplates: true, enableIndexStats: true, + editableIndexSettings: true, }, schema: schemaLatest, deprecations: ({ unused }) => [unused('dev.enableIndexDetailsPage', { level: 'warning' })], From 8775e96e081a8e063caccabaeafdc9ed0dee5542 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 18 Oct 2023 10:10:16 -0400 Subject: [PATCH 41/49] upgrade undici (#169019) ## Summary Upgrade `undici` from 5.22.1 to 5.26.3 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- yarn.lock | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/yarn.lock b/yarn.lock index c27be1a28c601..1dcea68abfd5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2180,6 +2180,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6" integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== +"@fastify/busboy@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" + integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== + "@foliojs-fork/fontkit@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@foliojs-fork/fontkit/-/fontkit-1.9.1.tgz#8124649168eb5273f580f66697a139fb5041296b" @@ -12281,13 +12286,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -busboy@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" - integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== - dependencies: - streamsearch "^1.1.0" - byte-size@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.0.tgz#6353d0bc14ab7a69abcefbf11f8db0145a862cb5" @@ -28160,11 +28158,6 @@ stream-slicer@0.0.6: resolved "https://registry.yarnpkg.com/stream-slicer/-/stream-slicer-0.0.6.tgz#f86b2ac5c2440b7a0a87b71f33665c0788046138" integrity sha1-+GsqxcJEC3oKh7cfM2ZcB4gEYTg= -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.0.tgz#f58c92e6f726b5390dcabd6dd9094d29a854d698" @@ -29581,11 +29574,11 @@ unc-path-regex@^0.1.2: integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= undici@^5.21.2, undici@^5.22.1: - version "5.22.1" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" - integrity sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw== + version "5.26.3" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.3.tgz#ab3527b3d5bb25b12f898dfd22165d472dd71b79" + integrity sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw== dependencies: - busboy "^1.6.0" + "@fastify/busboy" "^2.0.0" unfetch@^4.2.0: version "4.2.0" From 54fd40326d8121f4515d960433862d2de1c728e0 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 18 Oct 2023 16:52:55 +0200 Subject: [PATCH 42/49] [UnifiedDocViewer] Field search via wildcard (#168616) - Closes https://github.com/elastic/kibana/issues/168607 ## Summary This PR allows to search in DocViewer not only for partial matches but also for wildcard matches. Screenshot 2023-10-11 at 16 51 36 Screenshot 2023-10-11 at 16 51 48 Screenshot 2023-10-11 at 16 52 24 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Matthias Wilhelm --- packages/kbn-field-utils/index.ts | 4 + .../field_name_wildcard_matcher.test.tsx | 83 ++++++++++++++ .../src/utils/field_name_wildcard_matcher.ts | 20 ++++ .../src/components/field_name/field_name.tsx | 2 +- .../field_item_button/field_item_button.tsx | 17 +-- .../src/hooks/use_field_filters.ts | 3 +- .../field_name_wildcard_matcher.test.tsx | 51 --------- .../components/doc_viewer_table/table.tsx | 17 ++- .../apps/discover/group3/_doc_viewer.ts | 103 ++++++++++++++++++ test/functional/apps/discover/group3/index.ts | 1 + test/functional/page_objects/discover_page.ts | 5 + 11 files changed, 233 insertions(+), 73 deletions(-) create mode 100644 packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.test.tsx rename packages/{kbn-unified-field-list => kbn-field-utils}/src/utils/field_name_wildcard_matcher.ts (73%) delete mode 100644 packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.test.tsx create mode 100644 test/functional/apps/discover/group3/_doc_viewer.ts diff --git a/packages/kbn-field-utils/index.ts b/packages/kbn-field-utils/index.ts index c9f85c9cfcec5..1ad361feced99 100644 --- a/packages/kbn-field-utils/index.ts +++ b/packages/kbn-field-utils/index.ts @@ -18,5 +18,9 @@ export { getFieldIconType } from './src/utils/get_field_icon_type'; export { getFieldType } from './src/utils/get_field_type'; export { getFieldTypeDescription } from './src/utils/get_field_type_description'; export { getFieldTypeName, UNKNOWN_FIELD_TYPE_MESSAGE } from './src/utils/get_field_type_name'; +export { + fieldNameWildcardMatcher, + getFieldSearchMatchingHighlight, +} from './src/utils/field_name_wildcard_matcher'; export { FieldIcon, type FieldIconProps, getFieldIconProps } from './src/components/field_icon'; diff --git a/packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.test.tsx b/packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.test.tsx new file mode 100644 index 0000000000000..1311d1c5bbc2f --- /dev/null +++ b/packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 { type DataViewField } from '@kbn/data-views-plugin/common'; +import { + fieldNameWildcardMatcher, + getFieldSearchMatchingHighlight, +} from './field_name_wildcard_matcher'; + +const name = 'test.this_value.maybe'; +describe('fieldNameWildcardMatcher', function () { + describe('fieldNameWildcardMatcher()', () => { + it('should work correctly with wildcard', async () => { + expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, 'no')).toBe(false); + expect( + fieldNameWildcardMatcher({ displayName: 'test', name: 'yes' } as DataViewField, 'yes') + ).toBe(true); + + const search = 'test*ue'; + expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, search)).toBe( + false + ); + expect(fieldNameWildcardMatcher({ displayName: 'test.value' } as DataViewField, search)).toBe( + true + ); + expect(fieldNameWildcardMatcher({ name: 'test.this_value' } as DataViewField, search)).toBe( + true + ); + expect(fieldNameWildcardMatcher({ name: 'message.test' } as DataViewField, search)).toBe( + false + ); + expect(fieldNameWildcardMatcher({ name } as DataViewField, search)).toBe(false); + expect(fieldNameWildcardMatcher({ name } as DataViewField, `${search}*`)).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, '*value*')).toBe(true); + }); + + it('should work correctly with spaces', async () => { + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test maybe ')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test maybe*')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test. this')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'this _value be')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'this')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, ' value ')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'be')).toBe(true); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test this here')).toBe(false); + expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test that')).toBe(false); + expect(fieldNameWildcardMatcher({ name } as DataViewField, ' ')).toBe(false); + expect(fieldNameWildcardMatcher({ name: 'geo.location3' } as DataViewField, '3')).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'geo_location3' } as DataViewField, 'geo 3')).toBe( + true + ); + }); + + it('should be case-insensitive', async () => { + expect(fieldNameWildcardMatcher({ name: 'Test' } as DataViewField, 'test')).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'test' } as DataViewField, 'Test')).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'tesT' } as DataViewField, 'Tes*')).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'tesT' } as DataViewField, 'tes*')).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'tesT' } as DataViewField, 't T')).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'tesT' } as DataViewField, 't t')).toBe(true); + }); + }); + + describe('getFieldSearchMatchingHighlight()', function () { + it('should correctly return only partial match', async () => { + expect(getFieldSearchMatchingHighlight('test this', 'test')).toBe('test'); + expect(getFieldSearchMatchingHighlight('test this', 'this')).toBe('this'); + expect(getFieldSearchMatchingHighlight('test this')).toBe(''); + }); + + it('should correctly return a full match for a wildcard search', async () => { + expect(getFieldSearchMatchingHighlight('Test this', 'test*')).toBe('Test this'); + expect(getFieldSearchMatchingHighlight('test this', '*this')).toBe('test this'); + expect(getFieldSearchMatchingHighlight('test this', ' te th')).toBe('test this'); + }); + }); +}); diff --git a/packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.ts b/packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.ts similarity index 73% rename from packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.ts rename to packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.ts index eadf5597b171b..063ca3a16e01a 100644 --- a/packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.ts +++ b/packages/kbn-field-utils/src/utils/field_name_wildcard_matcher.ts @@ -41,3 +41,23 @@ export const fieldNameWildcardMatcher = ( const regExp = makeRegEx(fieldSearchHighlight); return (!!field.displayName && regExp.test(field.displayName)) || regExp.test(field.name); }; + +/** + * Get `highlight` string to be used together with `EuiHighlight` + * @param displayName + * @param fieldSearchHighlight + */ +export function getFieldSearchMatchingHighlight( + displayName: string, + fieldSearchHighlight?: string +): string { + const searchHighlight = (fieldSearchHighlight || '').trim(); + if ( + (searchHighlight.includes('*') || searchHighlight.includes(' ')) && + fieldNameWildcardMatcher({ name: displayName }, searchHighlight) + ) { + return displayName; + } + + return searchHighlight; +} diff --git a/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx b/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx index 4a525d7136214..0228732709eb1 100644 --- a/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx +++ b/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx @@ -36,7 +36,7 @@ export function FieldName({ const typeName = getFieldTypeName(fieldType); const displayName = fieldMapping && fieldMapping.displayName ? fieldMapping.displayName : fieldName; - const tooltip = displayName !== fieldName ? `${fieldName} (${displayName})` : fieldName; + const tooltip = displayName !== fieldName ? `${displayName} (${fieldName})` : fieldName; const subTypeMulti = fieldMapping && getDataViewFieldSubtypeMulti(fieldMapping.spec); const isMultiField = !!subTypeMulti?.multi; diff --git a/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx b/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx index 8ef947fc7bb8f..6842fb8d3eeb4 100644 --- a/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx +++ b/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx @@ -12,9 +12,8 @@ import classnames from 'classnames'; import { FieldButton, type FieldButtonProps } from '@kbn/react-field'; import { EuiButtonIcon, EuiButtonIconProps, EuiHighlight, EuiIcon, EuiToolTip } from '@elastic/eui'; import type { DataViewField } from '@kbn/data-views-plugin/common'; -import { FieldIcon, getFieldIconProps } from '@kbn/field-utils'; +import { FieldIcon, getFieldIconProps, getFieldSearchMatchingHighlight } from '@kbn/field-utils'; import { type FieldListItem, type GetCustomFieldType } from '../../types'; -import { fieldNameWildcardMatcher } from '../../utils/field_name_wildcard_matcher'; import './field_item_button.scss'; /** @@ -197,7 +196,7 @@ export function FieldItemButton({ fieldIcon={} fieldName={ @@ -233,15 +232,3 @@ function FieldConflictInfoIcon() { ); } - -function getSearchHighlight(displayName: string, fieldSearchHighlight?: string): string { - const searchHighlight = (fieldSearchHighlight || '').trim(); - if ( - (searchHighlight.includes('*') || searchHighlight.includes(' ')) && - fieldNameWildcardMatcher({ name: displayName }, searchHighlight) - ) { - return displayName; - } - - return searchHighlight; -} diff --git a/packages/kbn-unified-field-list/src/hooks/use_field_filters.ts b/packages/kbn-unified-field-list/src/hooks/use_field_filters.ts index badf101195d9e..d507c20c6049e 100644 --- a/packages/kbn-unified-field-list/src/hooks/use_field_filters.ts +++ b/packages/kbn-unified-field-list/src/hooks/use_field_filters.ts @@ -10,10 +10,9 @@ import { useMemo, useState } from 'react'; import { htmlIdGenerator } from '@elastic/eui'; import { type DataViewField } from '@kbn/data-views-plugin/common'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; -import { type FieldTypeKnown, getFieldIconType } from '@kbn/field-utils'; +import { type FieldTypeKnown, getFieldIconType, fieldNameWildcardMatcher } from '@kbn/field-utils'; import { type FieldListFiltersProps } from '../components/field_list_filters'; import { type FieldListItem, GetCustomFieldType } from '../types'; -import { fieldNameWildcardMatcher } from '../utils/field_name_wildcard_matcher'; const htmlId = htmlIdGenerator('fieldList'); diff --git a/packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.test.tsx b/packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.test.tsx deleted file mode 100644 index 56ee45f5eb207..0000000000000 --- a/packages/kbn-unified-field-list/src/utils/field_name_wildcard_matcher.test.tsx +++ /dev/null @@ -1,51 +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 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 { type DataViewField } from '@kbn/data-views-plugin/common'; -import { fieldNameWildcardMatcher } from './field_name_wildcard_matcher'; - -const name = 'test.this_value.maybe'; -describe('UnifiedFieldList fieldNameWildcardMatcher()', () => { - it('should work correctly with wildcard', async () => { - expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, 'no')).toBe(false); - expect( - fieldNameWildcardMatcher({ displayName: 'test', name: 'yes' } as DataViewField, 'yes') - ).toBe(true); - - const search = 'test*ue'; - expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, search)).toBe(false); - expect(fieldNameWildcardMatcher({ displayName: 'test.value' } as DataViewField, search)).toBe( - true - ); - expect(fieldNameWildcardMatcher({ name: 'test.this_value' } as DataViewField, search)).toBe( - true - ); - expect(fieldNameWildcardMatcher({ name: 'message.test' } as DataViewField, search)).toBe(false); - expect(fieldNameWildcardMatcher({ name } as DataViewField, search)).toBe(false); - expect(fieldNameWildcardMatcher({ name } as DataViewField, `${search}*`)).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, '*value*')).toBe(true); - }); - - it('should work correctly with spaces', async () => { - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test maybe ')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test maybe*')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test. this')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'this _value be')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'this')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, ' value ')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'be')).toBe(true); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test this here')).toBe(false); - expect(fieldNameWildcardMatcher({ name } as DataViewField, 'test that')).toBe(false); - expect(fieldNameWildcardMatcher({ name } as DataViewField, ' ')).toBe(false); - expect(fieldNameWildcardMatcher({ name: 'geo.location3' } as DataViewField, '3')).toBe(true); - expect(fieldNameWildcardMatcher({ name: 'geo_location3' } as DataViewField, 'geo 3')).toBe( - true - ); - }); -}); diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx index 66a40991da9cb..5a53f8a8eee50 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx @@ -38,6 +38,7 @@ import { isNestedFieldParent, usePager, } from '@kbn/discover-utils'; +import { fieldNameWildcardMatcher, getFieldSearchMatchingHighlight } from '@kbn/field-utils'; import type { DocViewRenderProps, FieldRecordLegacy } from '@kbn/unified-doc-viewer/types'; import { FieldName } from '@kbn/unified-doc-viewer'; import { useUnifiedDocViewerServices } from '../../hooks'; @@ -246,8 +247,13 @@ export const DocViewerTable = ({ acc.pinnedItems.push(fieldToItem(curFieldName)); } else { const fieldMapping = mapping(curFieldName); - const displayName = fieldMapping?.displayName ?? curFieldName; - if (displayName.toLowerCase().includes(searchText.toLowerCase())) { + if ( + !searchText?.trim() || + fieldNameWildcardMatcher( + { name: curFieldName, displayName: fieldMapping?.displayName }, + searchText + ) + ) { // filter only unpinned fields acc.restItems.push(fieldToItem(curFieldName)); } @@ -318,7 +324,6 @@ export const DocViewerTable = ({ const renderRows = useCallback( (items: FieldRecord[]) => { - const highlight = searchText?.toLowerCase(); return items.map( ({ action: { flattenedField, onFilter }, @@ -362,7 +367,10 @@ export const DocViewerTable = ({ fieldType={fieldType} fieldMapping={fieldMapping} scripted={scripted} - highlight={highlight} + highlight={getFieldSearchMatchingHighlight( + fieldMapping?.displayName ?? field, + searchText + )} />
diff --git a/test/functional/apps/discover/group3/_doc_viewer.ts b/test/functional/apps/discover/group3/_doc_viewer.ts new file mode 100644 index 0000000000000..8fa3a94b969e3 --- /dev/null +++ b/test/functional/apps/discover/group3/_doc_viewer.ts @@ -0,0 +1,103 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'header']); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const retry = getService('retry'); + const dataGrid = getService('dataGrid'); + + describe('discover doc viewer', function describeIndexTests() { + before(async function () { + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + beforeEach(async () => { + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.uiSettings.replace({ + defaultIndex: 'logstash-*', + hideAnnouncements: true, + }); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + afterEach(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.uiSettings.replace({}); + }); + + describe('search', function () { + const itemsPerPage = 25; + + beforeEach(async () => { + await dataGrid.clickRowToggle(); + await PageObjects.discover.isShowingDocViewer(); + await retry.waitFor('rendered items', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === itemsPerPage; + }); + }); + + afterEach(async () => { + const fieldSearch = await testSubjects.find('clearSearchButton'); + await fieldSearch.click(); + }); + + it('should be able to search by string', async function () { + await PageObjects.discover.findFieldByNameInDocViewer('geo'); + + await retry.waitFor('first updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === 4; + }); + + await PageObjects.discover.findFieldByNameInDocViewer('.s'); + + await retry.waitFor('second updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === 2; + }); + }); + + it('should be able to search by wildcard', async function () { + await PageObjects.discover.findFieldByNameInDocViewer('relatedContent*image'); + + await retry.waitFor('updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === 2; + }); + }); + + it('should be able to search with spaces as wildcard', async function () { + await PageObjects.discover.findFieldByNameInDocViewer('relatedContent image'); + + await retry.waitFor('updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === 4; + }); + }); + + it('should ignore empty search', async function () { + await PageObjects.discover.findFieldByNameInDocViewer(' '); // only spaces + + await retry.waitFor('the clear button', async () => { + return await testSubjects.exists('clearSearchButton'); + }); + + // expect no changes in the list + await retry.waitFor('all items', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === itemsPerPage; + }); + }); + }); + }); +} diff --git a/test/functional/apps/discover/group3/index.ts b/test/functional/apps/discover/group3/index.ts index 3a10a8ec6f768..9af02c006b14b 100644 --- a/test/functional/apps/discover/group3/index.ts +++ b/test/functional/apps/discover/group3/index.ts @@ -23,5 +23,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_drag_drop')); loadTestFile(require.resolve('./_sidebar')); loadTestFile(require.resolve('./_request_counts')); + loadTestFile(require.resolve('./_doc_viewer')); }); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index d36cd4b56b129..605ea816ac1e6 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -363,6 +363,11 @@ export class DiscoverPageObject extends FtrService { return await this.find.byClassName('monaco-editor'); } + public async findFieldByNameInDocViewer(name: string) { + const fieldSearch = await this.testSubjects.find('unifiedDocViewerFieldsSearchInput'); + await fieldSearch.type(name); + } + public async getMarks() { const table = await this.docTable.getTable(); const marks = await table.findAllByTagName('mark'); From 35038c1cb187d7db35cf4b4274a0fba5130c9791 Mon Sep 17 00:00:00 2001 From: Joseph Crail Date: Wed, 18 Oct 2023 07:53:53 -0700 Subject: [PATCH 43/49] [Profiling] Fix frame group ID when source filename is empty (#168905) This PR addresses an issue identified after the port from Kibana in https://github.com/elastic/elasticsearch/pull/99091: - update `createFrameGroupID` to use `fileID`+`functionName` in ID instead of `exeFilename`+`functionName` if the source filename is empty --- packages/kbn-profiling-utils/common/frame_group.test.ts | 4 ++-- packages/kbn-profiling-utils/common/frame_group.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kbn-profiling-utils/common/frame_group.test.ts b/packages/kbn-profiling-utils/common/frame_group.test.ts index b6bfa6161a175..0a2d1eb7b05a4 100644 --- a/packages/kbn-profiling-utils/common/frame_group.test.ts +++ b/packages/kbn-profiling-utils/common/frame_group.test.ts @@ -40,7 +40,7 @@ const elfSymbolizedTests = [ sourceFilename: '', functionName: 'strlen()', }, - expected: 'elf;libc;strlen()', + expected: 'elf;0x0123456789ABCDEF;strlen()', }, { params: { @@ -50,7 +50,7 @@ const elfSymbolizedTests = [ sourceFilename: '', functionName: 'strtok()', }, - expected: 'elf;libc;strtok()', + expected: 'elf;0xFEDCBA9876543210;strtok()', }, ]; diff --git a/packages/kbn-profiling-utils/common/frame_group.ts b/packages/kbn-profiling-utils/common/frame_group.ts index 56a190ee58062..32452ca293797 100644 --- a/packages/kbn-profiling-utils/common/frame_group.ts +++ b/packages/kbn-profiling-utils/common/frame_group.ts @@ -41,7 +41,7 @@ export function createFrameGroupID( } if (sourceFilename === '') { - return `elf;${exeFilename};${functionName}`; + return `elf;${fileID};${functionName}`; } return `full;${exeFilename};${functionName};${stripLeadingSubdirs(sourceFilename || '')}`; From be85fef3b2f1ef467c423c52d79c245eb015d9bc Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 15:59:51 +0100 Subject: [PATCH 44/49] skip flaky suite (#168772) --- .../e2e/investigations/dasbhoards/detection_response.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts index 0192d85bd6f23..8536ad215b17a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts @@ -43,7 +43,8 @@ import { ALERTS_URL, DASHBOARDS_URL, DETECTION_AND_RESPONSE_URL } from '../../.. const TEST_USER_NAME = 'test'; const SIEM_KIBANA_HOST_NAME = 'siem-kibana'; -describe('Detection response view', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/168772 +describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () => { before(() => { cleanKibana(); createRule(getNewRule()); From db6fe5140b937fe16bf683ed98465a7c710d0c02 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 16:01:06 +0100 Subject: [PATCH 45/49] skip flaky suite (#169091) --- .../e2e/detection_response/detection_alerts/alert_status.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts index 5d56239e74c99..ca90e9b72efd1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts @@ -29,6 +29,7 @@ import { visit } from '../../../tasks/navigation'; import { ALERTS_URL } from '../../../urls/navigation'; +// FLAKY: https://github.com/elastic/kibana/issues/169091 describe('Changing alert status', { tags: ['@ess', '@serverless'] }, () => { before(() => { cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); From 542c00878493aceb39aeec49c535f75cf722d95a Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 16:03:16 +0100 Subject: [PATCH 46/49] skip flaky suite (#169034) --- .../cypress/e2e/explore/host_details/risk_tab.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts index 6c5af8a1601c3..5a6653bc573e8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts @@ -17,7 +17,8 @@ import { RISK_INFORMATION_FLYOUT_HEADER } from '../../../screens/entity_analytic import { navigateToHostRiskDetailTab } from '../../../tasks/host_risk'; describe('risk tab', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { - describe('with legacy risk score', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169034 + describe.skip('with legacy risk score', () => { before(() => { cleanKibana(); // illegal_argument_exception: unknown setting [index.lifecycle.rollover_alias] From 9b2292a0b3293af9695896af6be7396c94788d22 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 16:04:34 +0100 Subject: [PATCH 47/49] skip flaky suite (#169033) --- .../cypress/e2e/explore/host_details/risk_tab.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts index 5a6653bc573e8..ea8362ee17541 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/host_details/risk_tab.cy.ts @@ -17,6 +17,7 @@ import { RISK_INFORMATION_FLYOUT_HEADER } from '../../../screens/entity_analytic import { navigateToHostRiskDetailTab } from '../../../tasks/host_risk'; describe('risk tab', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { + // FLAKY: https://github.com/elastic/kibana/issues/169033 // FLAKY: https://github.com/elastic/kibana/issues/169034 describe.skip('with legacy risk score', () => { before(() => { From 44d28830935c0c733809be1fb09838c25a73c189 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 16:05:42 +0100 Subject: [PATCH 48/49] skip flaky suite (#169093) --- .../cypress/e2e/investigations/timelines/esql/esql_state.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/esql_state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/esql_state.cy.ts index ef79bf8bf4d48..23396da6e3174 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/esql_state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/esql_state.cy.ts @@ -29,7 +29,8 @@ const INITIAL_END_DATE = 'Jan 19, 2024 @ 20:33:29.186'; const DEFAULT_ESQL_QUERY = 'from .alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-* | limit 10'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/169093 +describe.skip( 'Timeline Discover ESQL State', { tags: ['@ess'], From 9a9c21bfa5efd5f6146ced7ffc76dff11be08bb7 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Oct 2023 16:06:58 +0100 Subject: [PATCH 49/49] skip flaky suite (#168771) --- .../e2e/investigations/dasbhoards/detection_response.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts index 8536ad215b17a..fd7d1ec688d14 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts @@ -44,6 +44,7 @@ const TEST_USER_NAME = 'test'; const SIEM_KIBANA_HOST_NAME = 'siem-kibana'; // FLAKY: https://github.com/elastic/kibana/issues/168772 +// FLAKY: https://github.com/elastic/kibana/issues/168771 describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () => { before(() => { cleanKibana();